diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cdde5c729547..3258d9118e18 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,15 +88,14 @@ present in the framework. 1. Preserve existing formatting; i.e. do not reformat code for its own sake 1. Search the codebase using `git grep` and other tools to discover common naming conventions, etc. -1. Latin-1 (ISO-8859-1) encoding for Java sources; use `native2ascii` to convert - if necessary +1. UTF-8 encoding for Java sources ### Add Apache license header to all new classes ```java /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -124,11 +123,11 @@ modified a file in 2015 whose header still reads: * Copyright 2002-2011 the original author or authors. ``` -Then be sure to update it to 2015 accordingly: +Then be sure to update it to 2016 accordingly: ```java /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. ``` ### Use @since tags for newly-added public API types and methods diff --git a/build.gradle b/build.gradle index e9f47cf2ed02..bd1f4ec032d2 100644 --- a/build.gradle +++ b/build.gradle @@ -32,51 +32,52 @@ configure(allprojects) { project -> version = qualifyVersionIfNecessary(version) ext.aspectjVersion = "1.8.9" - ext.caffeineVersion = "2.3.1" + ext.caffeineVersion = "2.3.5" ext.eclipselinkVersion = "2.4.2" - ext.ehcacheVersion = "2.10.2" + ext.ehcacheVersion = "2.10.4" ext.ehcachejcacheVersion = "1.0.1" - ext.ehcache3Version = "3.1.1" + ext.ehcache3Version = "3.1.4" ext.ejbVersion = "3.0" - ext.fileuploadVersion = "1.3.2" + ext.fileuploadVersion = "1.3.3" ext.freemarkerVersion = "2.3.23" - ext.groovyVersion = "2.4.7" - ext.gsonVersion = "2.7" - ext.guavaVersion = "19.0" + ext.groovyVersion = "2.4.15" + ext.gsonVersion = "2.8.5" + ext.guavaVersion = "20.0" ext.hamcrestVersion = "1.3" ext.hibernate3Version = "3.6.10.Final" ext.hibernate4Version = "4.3.11.Final" - ext.hibernate5Version = "5.2.1.Final" + ext.hibernate5Version = "5.2.10.Final" ext.hibval4Version = "4.3.2.Final" - ext.hibval5Version = "5.2.4.Final" + ext.hibval5Version = "5.2.5.Final" ext.hsqldbVersion = "2.3.4" - ext.httpasyncVersion = "4.1.2" - ext.httpclientVersion = "4.5.2" - ext.jackson2Version = "2.8.1" + ext.httpasyncVersion = "4.1.4" + ext.httpclientVersion = "4.5.6" + ext.jackson2Version = "2.8.11.3" ext.jasperreportsVersion = "6.2.1" // our tests fail with JR-internal NPEs against 6.2.2 and higher - ext.javamailVersion = "1.5.5" - ext.jettyVersion = "9.3.11.v20160721" - ext.jodaVersion = "2.9.4" - ext.jrubyVersion = "1.7.25" // JRuby 9000 only supported through JSR-223 (StandardScriptFactory) + ext.javamailVersion = "1.5.6" + ext.jettyVersion = "9.3.14.v20161028" // as of 9.3.15, Jetty has hard Servlet 3.1 requirement + ext.jetty94Version = "9.4.6.v20170531" // for spring-websocket support, optimized for Jetty 9.4 + ext.jodaVersion = "2.9.9" + ext.jrubyVersion = "1.7.27" // JRuby 9000 primarily supported through JSR-223 (StandardScriptFactory) ext.jtaVersion = "1.2" ext.junitVersion = "4.12" ext.log4jVersion = "1.2.17" - ext.nettyVersion = "4.1.4.Final" + ext.nettyVersion = "4.1.31.Final" ext.okhttpVersion = "2.7.5" - ext.okhttp3Version = "3.4.1" - ext.openjpaVersion = "2.4.1" + ext.okhttp3Version = "3.8.1" + ext.openjpaVersion = "2.4.2" ext.poiVersion = "3.14" ext.reactorVersion = "2.0.8.RELEASE" - ext.romeVersion = "1.6.0" - ext.slf4jVersion = "1.7.21" + ext.romeVersion = "1.7.4" + ext.slf4jVersion = "1.7.25" ext.snakeyamlVersion = "1.17" - ext.snifferVersion = "1.15" + ext.snifferVersion = "1.17" ext.testngVersion = "6.9.10" ext.tiles2Version = "2.2.2" - ext.tiles3Version = "3.0.5" - ext.tomcatVersion = "8.5.4" + ext.tiles3Version = "3.0.8" + ext.tomcatVersion = "8.5.35" ext.tyrusVersion = "1.3.5" // constrained by WebLogic 12.1.3 support - ext.undertowVersion = "1.3.23.Final" + ext.undertowVersion = "1.3.33.Final" ext.xmlunitVersion = "1.6" ext.xstreamVersion = "1.4.9" @@ -110,11 +111,13 @@ configure(allprojects) { project -> compileJava { sourceCompatibility = 1.6 targetCompatibility = 1.6 + options.encoding = 'UTF-8' } compileTestJava { sourceCompatibility = 1.8 targetCompatibility = 1.8 + options.encoding = 'UTF-8' options.compilerArgs += "-parameters" } @@ -200,9 +203,9 @@ configure(allprojects) { project -> "http://ehcache.org/apidocs/${ehcacheVersion}", "http://ehcache.org/apidocs/${ehcache3Version}", "http://quartz-scheduler.org/api/2.2.1/", - "http://fasterxml.github.io/jackson-core/javadoc/2.7/", - "http://fasterxml.github.io/jackson-databind/javadoc/2.7/", - "http://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.7/", + "http://fasterxml.github.io/jackson-core/javadoc/2.8/", + "http://fasterxml.github.io/jackson-databind/javadoc/2.8/", + "http://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.8/", "http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/" ] as String[] } @@ -214,7 +217,6 @@ configure(subprojects - project(":spring-build-src")) { subproject -> configurations { jacoco } - dependencies { jacoco("org.jacoco:org.jacoco.agent:0.7.5.201505241946:runtime") } @@ -248,16 +250,16 @@ configure(subprojects - project(":spring-build-src")) { subproject -> options.links(project.ext.javadocLinks) options.addStringOption('Xdoclint:none', '-quiet') - // suppress warnings due to cross-module @see and @link references; - // note that global 'api' task does display all warnings. + // Suppress warnings due to cross-module @see and @link references. + // Note that global 'api' task does display all warnings. logging.captureStandardError LogLevel.INFO - logging.captureStandardOutput LogLevel.INFO // suppress "## warnings" message + logging.captureStandardOutput LogLevel.INFO // suppress "## warnings" message } task sourcesJar(type: Jar, dependsOn: classes) { classifier = 'sources' from sourceSets.main.allSource - // don't include or exclude anything explicitly by default. See SPR-12085. + // Don't include or exclude anything explicitly by default. See SPR-12085. } task javadocJar(type: Jar) { @@ -273,6 +275,7 @@ configure(subprojects - project(":spring-build-src")) { subproject -> project("spring-build-src") { description = "Exposes gradle buildSrc for IDE support" + apply plugin: "groovy" dependencies { @@ -286,12 +289,11 @@ project("spring-build-src") { project("spring-core") { description = "Spring Core" - // As of Spring 4.0.3, spring-core includes asm 5.x and repackages cglib 3.2, inlining - // both into the spring-core jar. cglib 3.2 itself depends on asm 5.x and is therefore - // further transformed by the JarJar task to depend on org.springframework.asm; this - // avoids including two different copies of asm unnecessarily. - def cglibVersion = "3.2.4" - def objenesisVersion = "2.4" + // spring-core includes asm and repackages cglib, inlining both into the spring-core jar. + // cglib itself depends on asm and is therefore further transformed by the JarJar task to + // depend on org.springframework.asm; this avoids including two different copies of asm. + def cglibVersion = "3.2.6" + def objenesisVersion = "2.6" configurations { jarjar @@ -311,9 +313,9 @@ project("spring-core") { configurations.cglib.each { originalJar -> zipfileset(src: originalJar) } - // repackage net.sf.cglib => org.springframework.cglib + // Repackage net.sf.cglib => org.springframework.cglib rule(pattern: "net.sf.cglib.**", result: "org.springframework.cglib.@1") - // as mentioned above, transform cglib"s internal asm dependencies from + // As mentioned above, transform cglib's internal asm dependencies from // org.objectweb.asm => org.springframework.asm. Doing this counts on the // the fact that Spring and cglib depend on the same version of asm! rule(pattern: "org.objectweb.asm.**", result: "org.springframework.asm.@1") @@ -334,7 +336,7 @@ project("spring-core") { configurations.objenesis.each { originalJar -> zipfileset(src: originalJar) } - // repackage org.objenesis => org.springframework.objenesis + // Repackage org.objenesis => org.springframework.objenesis rule(pattern: "org.objenesis.**", result: "org.springframework.objenesis.@1") } } @@ -351,17 +353,17 @@ project("spring-core") { compile("commons-logging:commons-logging:1.2") optional("commons-codec:commons-codec:1.10") optional("org.aspectj:aspectjweaver:${aspectjVersion}") - optional("net.sf.jopt-simple:jopt-simple:5.0.2") + optional("net.sf.jopt-simple:jopt-simple:5.0.3") optional("log4j:log4j:${log4jVersion}") testCompile("org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}") testCompile("xmlunit:xmlunit:${xmlunitVersion}") - testCompile("com.fasterxml.woodstox:woodstox-core:5.0.2") { + testCompile("com.fasterxml.woodstox:woodstox-core:5.0.3") { exclude group: "stax", module: "stax-api" } } jar { - // inline repackaged cglib classes directly into the spring-core jar + // Inline repackaged cglib classes directly into spring-core jar dependsOn cglibRepackJar from(zipTree(cglibRepackJar.archivePath)) { include "org/springframework/cglib/**" @@ -390,6 +392,7 @@ project("spring-beans") { project("spring-beans-groovy") { description "Groovy Bean Definitions" + merge.into = project(":spring-beans") apply plugin: "groovy" @@ -398,8 +401,8 @@ project("spring-beans-groovy") { optional("org.codehaus.groovy:groovy-all:${groovyVersion}") } - // this module's Java and Groovy sources need to be compiled together - compileJava.enabled=false + // This module's Java and Groovy sources need to be compiled together. + compileJava.enabled = false sourceSets { main { groovy { @@ -465,26 +468,27 @@ project("spring-instrument-tomcat") { project("spring-context") { description = "Spring Context" + apply plugin: "groovy" dependencies { compile(project(":spring-aop")) compile(project(":spring-beans")) - compile(project(":spring-expression")) compile(project(":spring-core")) + compile(project(":spring-expression")) compile(files(project(":spring-core").cglibRepackJar)) optional(project(":spring-instrument")) optional("javax.inject:javax.inject:1") optional("javax.ejb:ejb-api:${ejbVersion}") optional("javax.enterprise.concurrent:javax.enterprise.concurrent-api:1.0") - optional("javax.money:money-api:1.0") + optional("javax.money:money-api:1.0.1") optional("org.eclipse.persistence:javax.persistence:2.0.0") optional("javax.validation:validation-api:1.0.0.GA") optional("org.hibernate:hibernate-validator:${hibval4Version}") optional("joda-time:joda-time:${jodaVersion}") optional("org.aspectj:aspectjweaver:${aspectjVersion}") optional("org.codehaus.groovy:groovy-all:${groovyVersion}") - optional("org.beanshell:bsh:2.0b4") + optional("org.beanshell:bsh:2.0b5") optional("org.jruby:jruby:${jrubyVersion}") testCompile("javax.inject:javax.inject-tck:1") testCompile("org.javamoney:moneta:1.1") @@ -494,13 +498,44 @@ project("spring-context") { } } +project("spring-oxm") { + description = "Spring Object/XML Marshalling" + apply from: "oxm.gradle" + + dependencies { + compile(project(":spring-beans")) + compile(project(":spring-core")) + optional("org.codehaus.castor:castor-xml:1.4.1") { + exclude group: 'stax', module: 'stax-api' + exclude group: "org.springframework", module: "spring-context" + } + optional("org.apache.xmlbeans:xmlbeans:2.6.0") { + exclude group: 'stax', module: 'stax-api' + } + optional("com.thoughtworks.xstream:xstream:${xstreamVersion}") { + exclude group: 'xpp3', module: 'xpp3_min' + exclude group: 'xmlpull', module: 'xmlpull' + } + optional("org.jibx:jibx-run:1.2.6") + testCompile(project(":spring-context")) + testCompile("org.ogce:xpp3:1.1.6") + testCompile("org.codehaus.jettison:jettison:1.3.8") { + exclude group: 'stax', module: 'stax-api' + } + testCompile("xmlunit:xmlunit:${xmlunitVersion}") + testCompile(files(genCastor.classesDir).builtBy(genCastor)) + testCompile(files(genJaxb.classesDir).builtBy(genJaxb)) + testCompile(files(genXmlbeans.classesDir).builtBy(genXmlbeans)) + } +} + project("spring-messaging") { description = "Spring Messaging" dependencies { compile(project(":spring-beans")) - compile(project(":spring-core")) compile(project(":spring-context")) + compile(project(":spring-core")) optional(project(":spring-oxm")) optional("io.projectreactor:reactor-core:${reactorVersion}") optional("io.projectreactor:reactor-net:${reactorVersion}") { @@ -552,45 +587,14 @@ project("spring-tx") { } } -project("spring-oxm") { - description = "Spring Object/XML Marshalling" - apply from: "oxm.gradle" - - dependencies { - compile(project(":spring-beans")) - compile(project(":spring-core")) - optional("org.codehaus.castor:castor-xml:1.4.1") { - exclude group: 'stax', module: 'stax-api' - exclude group: "org.springframework", module: "spring-context" - } - optional("org.apache.xmlbeans:xmlbeans:2.6.0") { - exclude group: 'stax', module: 'stax-api' - } - optional("com.thoughtworks.xstream:xstream:${xstreamVersion}") { - exclude group: 'xpp3', module: 'xpp3_min' - exclude group: 'xmlpull', module: 'xmlpull' - } - optional("org.jibx:jibx-run:1.2.6") - testCompile(project(":spring-context")) - testCompile("xmlunit:xmlunit:${xmlunitVersion}") - testCompile("xpp3:xpp3:1.1.4c") - testCompile("org.codehaus.jettison:jettison:1.3.7") { - exclude group: 'stax', module: 'stax-api' - } - testCompile(files(genCastor.classesDir).builtBy(genCastor)) - testCompile(files(genJaxb.classesDir).builtBy(genJaxb)) - testCompile(files(genXmlbeans.classesDir).builtBy(genXmlbeans)) - } -} - project("spring-jms") { description = "Spring JMS" dependencies { - compile(project(":spring-core")) - compile(project(":spring-beans")) compile(project(":spring-aop")) + compile(project(":spring-beans")) compile(project(":spring-context")) + compile(project(":spring-core")) compile(project(":spring-messaging")) compile(project(":spring-tx")) provided("javax.jms:jms-api:1.1-rev-1") @@ -612,9 +616,9 @@ project("spring-jdbc") { optional("javax.transaction:javax.transaction-api:${jtaVersion}") optional("com.mchange:c3p0:0.9.5.2") optional("org.hsqldb:hsqldb:${hsqldbVersion}") - optional("com.h2database:h2:1.4.192") - optional("org.apache.derby:derby:10.12.1.1") - optional("org.apache.derby:derbyclient:10.12.1.1") + optional("com.h2database:h2:1.4.193") + optional("org.apache.derby:derby:10.13.1.1") + optional("org.apache.derby:derbyclient:10.13.1.1") } } @@ -622,9 +626,9 @@ project("spring-context-support") { description = "Spring Context Support" dependencies { - compile(project(":spring-core")) compile(project(":spring-beans")) compile(project(":spring-context")) + compile(project(":spring-core")) optional(project(":spring-jdbc")) // for Quartz support optional(project(":spring-tx")) // for Quartz support optional("javax.mail:javax.mail-api:${javamailVersion}") @@ -653,11 +657,14 @@ project("spring-context-support") { testCompile("org.slf4j:slf4j-api:${slf4jVersion}") testRuntime("com.sun.mail:javax.mail:${javamailVersion}") testRuntime("org.ehcache:jcache:${ehcachejcacheVersion}") + testRuntime("org.ehcache:ehcache:${ehcache3Version}") + testRuntime("org.terracotta:management-model:2.3.0") } } project("spring-web") { description = "Spring Web" + apply plugin: "groovy" // Re-generate Protobuf classes from *.proto files and move them in test sources @@ -696,7 +703,7 @@ project("spring-web") { optional("com.squareup.okhttp:okhttp:${okhttpVersion}") optional("com.squareup.okhttp3:okhttp:${okhttp3Version}") optional("com.fasterxml.jackson.core:jackson-databind:${jackson2Version}") - optional("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:${jackson2Version}") + optional("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.8.11") optional("com.google.code.gson:gson:${gsonVersion}") optional("com.rometools:rome:${romeVersion}") optional("org.eclipse.jetty:jetty-servlet:${jettyVersion}") { @@ -712,12 +719,14 @@ project("spring-web") { testCompile(project(":spring-context-support")) // for JafMediaTypeFactory testCompile("xmlunit:xmlunit:${xmlunitVersion}") testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}") + testCompile("org.skyscreamer:jsonassert:1.4.0") testCompile("org.apache.taglibs:taglibs-standard-jstlel:1.2.1") { exclude group: "org.apache.taglibs", module: "taglibs-standard-spec" } - testCompile("com.fasterxml.jackson.datatype:jackson-datatype-joda:${jackson2Version}") - testCompile("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:${jackson2Version}") - testCompile("com.fasterxml.jackson.module:jackson-module-kotlin:${jackson2Version}") + testCompile("com.fasterxml.jackson.datatype:jackson-datatype-joda:2.8.11") + testCompile("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.8.11") + testCompile("com.fasterxml.jackson.module:jackson-module-kotlin:2.8.11.1") + testCompile("com.squareup.okhttp3:mockwebserver:${okhttp3Version}") testRuntime("com.sun.mail:javax.mail:${javamailVersion}") } } @@ -827,7 +836,7 @@ project("spring-webmvc") { exclude group: "org.springframework", module: "spring-context" } optional("com.fasterxml.jackson.core:jackson-databind:${jackson2Version}") - optional("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:${jackson2Version}") + optional("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.8.11") optional("com.rometools:rome:${romeVersion}") optional("javax.el:javax.el-api:2.2.5") optional("org.apache.tiles:tiles-api:${tiles3Version}") @@ -847,7 +856,7 @@ project("spring-webmvc") { exclude group: "org.slf4j", module: "jcl-over-slf4j" exclude group: "org.springframework", module: "spring-web" } - optional('org.webjars:webjars-locator:0.32') + optional('org.webjars:webjars-locator:0.32-1') testCompile("xmlunit:xmlunit:${xmlunitVersion}") testCompile("dom4j:dom4j:1.6.1") { exclude group: "xml-apis", module: "xml-apis" @@ -867,10 +876,10 @@ project("spring-webmvc") { testCompile("org.hibernate:hibernate-validator:${hibval4Version}") testCompile("org.apache.httpcomponents:httpclient:${httpclientVersion}") testCompile("commons-fileupload:commons-fileupload:${fileuploadVersion}") - testCompile("commons-io:commons-io:1.3") + testCompile("commons-io:commons-io:1.4") testCompile("joda-time:joda-time:${jodaVersion}") testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}") - testCompile("org.mozilla:rhino:1.7.7.1") + testCompile("org.mozilla:rhino:1.7.7.2") testRuntime("org.jruby:jruby:${jrubyVersion}") testRuntime("org.python:jython-standalone:2.5.3") testRuntime("org.webjars:underscorejs:1.8.3") @@ -928,8 +937,8 @@ project("spring-websocket") { description = "Spring WebSocket" dependencies { - compile(project(":spring-core")) compile(project(":spring-context")) + compile(project(":spring-core")) compile(project(":spring-web")) optional(project(":spring-messaging")) optional(project(":spring-webmvc")) @@ -946,11 +955,11 @@ project("spring-websocket") { optional("org.eclipse.jetty:jetty-webapp:${jettyVersion}") { exclude group: "javax.servlet", module: "javax.servlet" } - optional("org.eclipse.jetty.websocket:websocket-server:${jettyVersion}") { + optional("org.eclipse.jetty.websocket:websocket-server:${jetty94Version}") { exclude group: "javax.servlet", module: "javax.servlet" } - optional("org.eclipse.jetty.websocket:websocket-client:${jettyVersion}") - optional("org.eclipse.jetty:jetty-client:${jettyVersion}") + optional("org.eclipse.jetty.websocket:websocket-client:${jetty94Version}") + optional("org.eclipse.jetty:jetty-client:${jetty94Version}") optional("io.undertow:undertow-core:${undertowVersion}") optional("io.undertow:undertow-servlet:${undertowVersion}") { exclude group: "org.jboss.spec.javax.servlet", module: "jboss-servlet-api_3.1_spec" @@ -974,11 +983,12 @@ project("spring-test") { dependencies { compile(project(":spring-core")) + optional(project(":spring-aop")) optional(project(":spring-beans")) optional(project(":spring-context")) optional(project(":spring-jdbc")) - optional(project(":spring-tx")) optional(project(":spring-orm")) + optional(project(":spring-tx")) optional(project(":spring-web")) optional(project(":spring-webmvc")) optional(project(":spring-webmvc-portlet")) @@ -999,11 +1009,11 @@ project("spring-test") { optional("org.codehaus.groovy:groovy-all:${groovyVersion}") optional("org.hamcrest:hamcrest-core:${hamcrestVersion}") optional("xmlunit:xmlunit:${xmlunitVersion}") - optional("net.sourceforge.htmlunit:htmlunit:2.22") - optional("org.seleniumhq.selenium:htmlunit-driver:2.21") + optional("net.sourceforge.htmlunit:htmlunit:2.23") + optional("org.seleniumhq.selenium:htmlunit-driver:2.23.2") optional("org.seleniumhq.selenium:selenium-java:2.53.1") - optional("org.skyscreamer:jsonassert:1.3.0") - optional("com.jayway.jsonpath:json-path:2.2.0") + optional("org.skyscreamer:jsonassert:1.4.0") + optional("com.jayway.jsonpath:json-path:2.3.0") testCompile(project(":spring-context-support")) testCompile(project(":spring-oxm")) testCompile("javax.mail:javax.mail-api:${javamailVersion}") @@ -1026,8 +1036,6 @@ project("spring-test") { testCompile("org.apache.httpcomponents:httpclient:${httpclientVersion}") testCompile("javax.cache:cache-api:1.0.0") testRuntime("log4j:log4j:${log4jVersion}") - testRuntime("org.ehcache:ehcache:${ehcache3Version}") - testRuntime("org.terracotta:management-model:2.0.0") } task testNG(type: Test) { @@ -1172,7 +1180,7 @@ configure(rootProject) { // don't publish the default jar for the root project configurations.archives.artifacts.clear() - dependencies { // for integration tests + dependencies { // for integration tests testCompile(project(":spring-aop")) testCompile(project(":spring-beans")) testCompile(project(":spring-context")) @@ -1221,11 +1229,11 @@ configure(rootProject) { doFirst { classpath = files( - // ensure Servlet 3.x and Hibernate 4.x have precedence on the javadoc + // Ensure Servlet 3.x and Hibernate 4.x have precedence on the javadoc // classpath over their respective 2.5 and 3.x variants project(":spring-webmvc").sourceSets.main.compileClasspath.files.find { it =~ "servlet-api" }, rootProject.sourceSets.test.compileClasspath.files.find { it =~ "hibernate-core" }, - // ensure the javadoc process can resolve types compiled from .aj sources + // Ensure the javadoc process can resolve types compiled from .aj sources project(":spring-aspects").sourceSets.main.output ) classpath += files(subprojects.collect { it.sourceSets.main.compileClasspath }) @@ -1358,7 +1366,7 @@ configure(rootProject) { task wrapper(type: Wrapper) { description = "Generates gradlew[.bat] scripts" - gradleVersion = "2.13" + gradleVersion = "2.14.1" doLast() { def gradleOpts = "-XX:MaxMetaspaceSize=1024m -Xmx1024m" diff --git a/gradle.properties b/gradle.properties index b61eae0b3b5f..08f289efc861 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=4.3.2.BUILD-SNAPSHOT +version=4.3.21.RELEASE diff --git a/gradle/publish-maven.gradle b/gradle/publish-maven.gradle index 1145e112a661..eff54387f693 100644 --- a/gradle/publish-maven.gradle +++ b/gradle/publish-maven.gradle @@ -29,8 +29,8 @@ def customizePom(pom, gradleProject) { } licenses { license { - name "The Apache Software License, Version 2.0" - url "http://www.apache.org/licenses/LICENSE-2.0.txt" + name "Apache License, Version 2.0" + url "http://www.apache.org/licenses/LICENSE-2.0" distribution "repo" } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ca78035ef050..3baa851b28c6 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 fc60887c40df..19e5389604fc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Jun 15 12:59:30 CEST 2016 +#Wed Aug 17 21:21:18 CEST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-bin.zip diff --git a/spring-aop/src/main/java/org/springframework/aop/MethodMatcher.java b/spring-aop/src/main/java/org/springframework/aop/MethodMatcher.java index 7cba7648cf43..8b528021882a 100644 --- a/spring-aop/src/main/java/org/springframework/aop/MethodMatcher.java +++ b/spring-aop/src/main/java/org/springframework/aop/MethodMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,10 +48,11 @@ public interface MethodMatcher { /** - * Perform static checking whether the given method matches. If this - * returns {@code false} or if the {@link #isRuntime()} method - * returns {@code false}, no runtime check (i.e. no. - * {@link #matches(java.lang.reflect.Method, Class, Object[])} call) will be made. + * Perform static checking whether the given method matches. + *

If this returns {@code false} or if the {@link #isRuntime()} + * method returns {@code false}, no runtime check (i.e. no + * {@link #matches(java.lang.reflect.Method, Class, Object[])} call) + * will be made. * @param method the candidate method * @param targetClass the target class (may be {@code null}, in which case * the candidate class must be taken to be the method's declaring class) diff --git a/spring-aop/src/main/java/org/springframework/aop/TargetSource.java b/spring-aop/src/main/java/org/springframework/aop/TargetSource.java index 8e5ccc1fa6c5..f23e16f38dbe 100644 --- a/spring-aop/src/main/java/org/springframework/aop/TargetSource.java +++ b/spring-aop/src/main/java/org/springframework/aop/TargetSource.java @@ -1,5 +1,5 @@ /*< - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,9 +34,8 @@ public interface TargetSource extends TargetClassAware { /** * Return the type of targets returned by this {@link TargetSource}. - *

Can return {@code null}, although certain usages of a - * {@code TargetSource} might just work with a predetermined - * target class. + *

Can return {@code null}, although certain usages of a {@code TargetSource} + * might just work with a predetermined target class. * @return the type of targets returned by this {@link TargetSource} */ @Override @@ -44,9 +43,8 @@ public interface TargetSource extends TargetClassAware { /** * Will all calls to {@link #getTarget()} return the same object? - *

In that case, there will be no need to invoke - * {@link #releaseTarget(Object)}, and the AOP framework can cache - * the return value of {@link #getTarget()}. + *

In that case, there will be no need to invoke {@link #releaseTarget(Object)}, + * and the AOP framework can cache the return value of {@link #getTarget()}. * @return {@code true} if the target is immutable * @see #getTarget */ @@ -55,14 +53,15 @@ public interface TargetSource extends TargetClassAware { /** * Return a target instance. Invoked immediately before the * AOP framework calls the "target" of an AOP method invocation. - * @return the target object, which contains the joinpoint + * @return the target object which contains the joinpoint, + * or {@code null} if there is no actual target instance * @throws Exception if the target object can't be resolved */ Object getTarget() throws Exception; /** * Release the given target object obtained from the - * {@link #getTarget()} method. + * {@link #getTarget()} method, if any. * @param target object obtained from a call to {@link #getTarget()} * @throws Exception if the object can't be released */ 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 c75751688998..898602433d65 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-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -103,8 +103,8 @@ public static JoinPoint currentJoinPoint() { private final AspectInstanceFactory aspectInstanceFactory; /** - * The name of the aspect (ref bean) in which this advice was defined (used - * when determining advice precedence so that we can determine + * The name of the aspect (ref bean) in which this advice was defined + * (used when determining advice precedence so that we can determine * whether two pieces of advice come from the same aspect). */ private String aspectName; @@ -118,13 +118,13 @@ public static JoinPoint currentJoinPoint() { * This will be non-null if the creator of this advice object knows the argument names * and sets them explicitly */ - private String[] argumentNames = null; + private String[] argumentNames; /** Non-null if after throwing advice binds the thrown value */ - private String throwingName = null; + private String throwingName; /** Non-null if after returning advice binds the return value */ - private String returningName = null; + private String returningName; private Class discoveredReturningType = Object.class; @@ -227,7 +227,7 @@ public String getAspectName() { } /** - * Sets the declaration order of this advice within the aspect + * Set the declaration order of this advice within the aspect. */ public void setDeclarationOrder(int order) { this.declarationOrder = order; @@ -295,8 +295,8 @@ protected void setReturningNameNoCheck(String name) { } catch (Throwable ex) { throw new IllegalArgumentException("Returning name '" + name + - "' is neither a valid argument name nor the fully-qualified name of a Java type on the classpath. " + - "Root cause: " + ex); + "' is neither a valid argument name nor the fully-qualified " + + "name of a Java type on the classpath. Root cause: " + ex); } } } @@ -329,8 +329,8 @@ protected void setThrowingNameNoCheck(String name) { } catch (Throwable ex) { throw new IllegalArgumentException("Throwing name '" + name + - "' is neither a valid argument name nor the fully-qualified name of a Java type on the classpath. " + - "Root cause: " + ex); + "' is neither a valid argument name nor the fully-qualified " + + "name of a Java type on the classpath. Root cause: " + ex); } } } @@ -366,7 +366,7 @@ private boolean isVariableName(String name) { * to which argument name. There are multiple strategies for determining * this binding, which are arranged in a ChainOfResponsibility. */ - public synchronized final void calculateArgumentBindings() { + public final synchronized void calculateArgumentBindings() { // The simple case... nothing to bind. if (this.argumentsIntrospected || this.parameterTypes.length == 0) { return; @@ -374,10 +374,8 @@ public synchronized final void calculateArgumentBindings() { int numUnboundArgs = this.parameterTypes.length; Class[] parameterTypes = this.aspectJAdviceMethod.getParameterTypes(); - if (maybeBindJoinPoint(parameterTypes[0]) || maybeBindProceedingJoinPoint(parameterTypes[0])) { - numUnboundArgs--; - } - else if (maybeBindJoinPointStaticPart(parameterTypes[0])) { + if (maybeBindJoinPoint(parameterTypes[0]) || maybeBindProceedingJoinPoint(parameterTypes[0]) || + maybeBindJoinPointStaticPart(parameterTypes[0])) { numUnboundArgs--; } 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 671951324b2c..fa044c178a3d 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-2012 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -113,6 +113,7 @@ * returning {@code null} in the case that the parameter names cannot be discovered. * * @author Adrian Colyer + * @author Juergen Hoeller * @since 2.0 */ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscoverer { @@ -154,23 +155,17 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov } + /** The pointcut expression associated with the advice, as a simple String */ + private String pointcutExpression; + private boolean raiseExceptions; - /** - * If the advice is afterReturning, and binds the return value, this is the parameter name used. - */ + /** If the advice is afterReturning, and binds the return value, this is the parameter name used */ private String returningName; - /** - * If the advice is afterThrowing, and binds the thrown value, this is the parameter name used. - */ + /** If the advice is afterThrowing, and binds the thrown value, this is the parameter name used */ private String throwingName; - /** - * The pointcut expression associated with the advice, as a simple String. - */ - private String pointcutExpression; - private Class[] argumentTypes; private String[] parameterNameBindings; @@ -186,6 +181,7 @@ public AspectJAdviceParameterNameDiscoverer(String pointcutExpression) { this.pointcutExpression = pointcutExpression; } + /** * Indicate whether {@link IllegalArgumentException} and {@link AmbiguousBindingException} * must be thrown as appropriate in the case of failing to deduce advice parameter names. @@ -213,6 +209,7 @@ public void setThrowingName(String throwingName) { this.throwingName = throwingName; } + /** * Deduce the parameter names for an advice method. *

See the {@link AspectJAdviceParameterNameDiscoverer class level javadoc} @@ -418,7 +415,7 @@ private void maybeBindAnnotationsFromPointcutExpression() { String[] tokens = StringUtils.tokenizeToStringArray(this.pointcutExpression, " "); for (int i = 0; i < tokens.length; i++) { String toMatch = tokens[i]; - int firstParenIndex = toMatch.indexOf("("); + int firstParenIndex = toMatch.indexOf('('); if (firstParenIndex != -1) { toMatch = toMatch.substring(0, firstParenIndex); } @@ -474,7 +471,7 @@ else if (numAnnotationSlots == 1) { * If the token starts meets Java identifier conventions, it's in. */ private String maybeExtractVariableName(String candidateToken) { - if (candidateToken == null || candidateToken.equals("")) { + if (!StringUtils.hasLength(candidateToken)) { return null; } if (Character.isJavaIdentifierStart(candidateToken.charAt(0)) && @@ -578,7 +575,7 @@ private void maybeBindReferencePointcutParameter() { if (toMatch.startsWith("!")) { toMatch = toMatch.substring(1); } - int firstParenIndex = toMatch.indexOf("("); + int firstParenIndex = toMatch.indexOf('('); if (firstParenIndex != -1) { toMatch = toMatch.substring(0, firstParenIndex); } 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 3cb261368909..84788bc64309 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-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,6 @@ import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.aspectj.weaver.BCException; import org.aspectj.weaver.patterns.NamePattern; import org.aspectj.weaver.reflect.ReflectionWorld.ReflectionWorldException; import org.aspectj.weaver.reflect.ShadowMatchImpl; @@ -187,13 +186,24 @@ private void checkReadyToMatch() { throw new IllegalStateException("Must set property 'expression' before attempting to match"); } if (this.pointcutExpression == null) { - this.pointcutClassLoader = (this.beanFactory instanceof ConfigurableBeanFactory ? - ((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader() : - ClassUtils.getDefaultClassLoader()); + this.pointcutClassLoader = determinePointcutClassLoader(); this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader); } } + /** + * Determine the ClassLoader to use for pointcut evaluation. + */ + private ClassLoader determinePointcutClassLoader() { + if (this.beanFactory instanceof ConfigurableBeanFactory) { + return ((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader(); + } + if (this.pointcutDeclarationScope != null) { + return this.pointcutDeclarationScope.getClassLoader(); + } + return ClassUtils.getDefaultClassLoader(); + } + /** * Build the underlying AspectJ pointcut expression. */ @@ -258,7 +268,7 @@ public boolean matches(Class targetClass) { } } } - catch (BCException ex) { + catch (Throwable ex) { logger.debug("PointcutExpression matching rejected target class", ex); } return false; @@ -326,7 +336,6 @@ public boolean matches(Method method, Class targetClass, Object... args) { } catch (IllegalStateException ex) { // No current invocation... - // TODO: Should we really proceed here? if (logger.isDebugEnabled()) { logger.debug("Could not access current invocation - matching with limited context: " + ex); } @@ -413,39 +422,46 @@ private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) { shadowMatch = this.shadowMatchCache.get(targetMethod); if (shadowMatch == null) { try { - shadowMatch = this.pointcutExpression.matchesMethodExecution(methodToMatch); - } - catch (ReflectionWorldException ex) { - // Failed to introspect target method, probably because it has been loaded - // in a special ClassLoader. Let's try the declaring ClassLoader instead... - try { - fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass()); - if (fallbackExpression != null) { - shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch); - } - } - catch (ReflectionWorldException ex2) { - fallbackExpression = null; - } - } - if (shadowMatch == null && targetMethod != originalMethod) { - methodToMatch = originalMethod; try { shadowMatch = this.pointcutExpression.matchesMethodExecution(methodToMatch); } - catch (ReflectionWorldException ex3) { - // Could neither introspect the target class nor the proxy class -> - // let's try the original method's declaring class before we give up... + catch (ReflectionWorldException ex) { + // Failed to introspect target method, probably because it has been loaded + // in a special ClassLoader. Let's try the declaring ClassLoader instead... try { fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass()); if (fallbackExpression != null) { shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch); } } - catch (ReflectionWorldException ex4) { + catch (ReflectionWorldException ex2) { fallbackExpression = null; } } + if (shadowMatch == null && targetMethod != originalMethod) { + methodToMatch = originalMethod; + try { + shadowMatch = this.pointcutExpression.matchesMethodExecution(methodToMatch); + } + catch (ReflectionWorldException ex3) { + // Could neither introspect the target class nor the proxy class -> + // let's try the original method's declaring class before we give up... + try { + fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass()); + if (fallbackExpression != null) { + shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch); + } + } + catch (ReflectionWorldException ex4) { + fallbackExpression = null; + } + } + } + } + catch (Throwable ex) { + // Possibly AspectJ 1.8.10 encountering an invalid signature + logger.debug("PointcutExpression matching rejected target method", ex); + fallbackExpression = null; } if (shadowMatch == null) { shadowMatch = new ShadowMatchImpl(org.aspectj.util.FuzzyBoolean.NO, null, null, null); diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcutAdvisor.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcutAdvisor.java index 9de941958c74..1c8975419546 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcutAdvisor.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcutAdvisor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ import org.springframework.aop.Pointcut; import org.springframework.aop.support.AbstractGenericPointcutAdvisor; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; /** * Spring AOP Advisor that can be used for any AspectJ pointcut expression. @@ -26,16 +28,11 @@ * @since 2.0 */ @SuppressWarnings("serial") -public class AspectJExpressionPointcutAdvisor extends AbstractGenericPointcutAdvisor { +public class AspectJExpressionPointcutAdvisor extends AbstractGenericPointcutAdvisor implements BeanFactoryAware { private final AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); - @Override - public Pointcut getPointcut() { - return this.pointcut; - } - public void setExpression(String expression) { this.pointcut.setExpression(expression); } @@ -52,12 +49,22 @@ public String getLocation() { return this.pointcut.getLocation(); } - public void setParameterTypes(Class[] types) { + public void setParameterNames(String... names) { + this.pointcut.setParameterNames(names); + } + + public void setParameterTypes(Class... types) { this.pointcut.setParameterTypes(types); } - public void setParameterNames(String... names) { - this.pointcut.setParameterNames(names); + @Override + public void setBeanFactory(BeanFactory beanFactory) { + this.pointcut.setBeanFactory(beanFactory); + } + + @Override + public Pointcut getPointcut() { + return this.pointcut; } } diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJPointcutAdvisor.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJPointcutAdvisor.java index 14ad6c67a91d..64ead7b69b31 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJPointcutAdvisor.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJPointcutAdvisor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ import org.springframework.aop.PointcutAdvisor; import org.springframework.core.Ordered; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; /** * AspectJPointcutAdvisor that adapts an {@link AbstractAspectJAdvice} @@ -51,11 +50,11 @@ public AspectJPointcutAdvisor(AbstractAspectJAdvice advice) { this.pointcut = advice.buildSafePointcut(); } + public void setOrder(int order) { this.order = order; } - @Override public boolean isPerInstance() { return true; @@ -91,12 +90,12 @@ public boolean equals(Object other) { return false; } AspectJPointcutAdvisor otherAdvisor = (AspectJPointcutAdvisor) other; - return (ObjectUtils.nullSafeEquals(this.advice, otherAdvisor.advice)); + return this.advice.equals(otherAdvisor.advice); } @Override public int hashCode() { - return AspectJPointcutAdvisor.class.hashCode(); + return AspectJPointcutAdvisor.class.hashCode() * 29 + this.advice.hashCode(); } } diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/DeclareParentsAdvisor.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/DeclareParentsAdvisor.java index 47e461551ba7..0c7b05d0d53f 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/DeclareParentsAdvisor.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/DeclareParentsAdvisor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import org.springframework.aop.ClassFilter; import org.springframework.aop.IntroductionAdvisor; +import org.springframework.aop.IntroductionInterceptor; import org.springframework.aop.support.ClassFilters; import org.springframework.aop.support.DelegatePerTargetObjectIntroductionInterceptor; import org.springframework.aop.support.DelegatingIntroductionInterceptor; @@ -34,12 +35,12 @@ */ public class DeclareParentsAdvisor implements IntroductionAdvisor { + private final Advice advice; + private final Class introducedInterface; private final ClassFilter typePatternClassFilter; - private final Advice advice; - /** * Create a new advisor for this DeclareParents field. @@ -48,8 +49,8 @@ public class DeclareParentsAdvisor implements IntroductionAdvisor { * @param defaultImpl the default implementation class */ public DeclareParentsAdvisor(Class interfaceType, String typePattern, Class defaultImpl) { - this(interfaceType, typePattern, defaultImpl, - new DelegatePerTargetObjectIntroductionInterceptor(defaultImpl, interfaceType)); + this(interfaceType, typePattern, + new DelegatePerTargetObjectIntroductionInterceptor(defaultImpl, interfaceType)); } /** @@ -59,8 +60,7 @@ public DeclareParentsAdvisor(Class interfaceType, String typePattern, Class interfaceType, String typePattern, Object delegateRef) { - this(interfaceType, typePattern, delegateRef.getClass(), - new DelegatingIntroductionInterceptor(delegateRef)); + this(interfaceType, typePattern, new DelegatingIntroductionInterceptor(delegateRef)); } /** @@ -68,23 +68,21 @@ public DeclareParentsAdvisor(Class interfaceType, String typePattern, Object * (cannot use method such as init() to share common code, due the use of final fields) * @param interfaceType static field defining the introduction * @param typePattern type pattern the introduction is restricted to - * @param implementationClass implementation class - * @param advice delegation advice + * @param interceptor the delegation advice as {@link IntroductionInterceptor} */ - private DeclareParentsAdvisor(Class interfaceType, String typePattern, Class implementationClass, Advice advice) { + private DeclareParentsAdvisor(Class interfaceType, String typePattern, IntroductionInterceptor interceptor) { + this.advice = interceptor; this.introducedInterface = interfaceType; - ClassFilter typePatternFilter = new TypePatternClassFilter(typePattern); // Excludes methods implemented. + ClassFilter typePatternFilter = new TypePatternClassFilter(typePattern); ClassFilter exclusion = new ClassFilter() { @Override public boolean matches(Class clazz) { - return !(introducedInterface.isAssignableFrom(clazz)); + return !introducedInterface.isAssignableFrom(clazz); } }; - this.typePatternClassFilter = ClassFilters.intersection(typePatternFilter, exclusion); - this.advice = advice; } 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 d6e43ec1fd13..fc606b21fc87 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-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,17 +32,15 @@ import org.springframework.util.Assert; /** - * Implementation of AspectJ ProceedingJoinPoint interface - * wrapping an AOP Alliance MethodInvocation. + * An implementation of the AspectJ {@link ProceedingJoinPoint} interface + * wrapping an AOP Alliance {@link org.aopalliance.intercept.MethodInvocation}. * - *

Note: the {@code getThis()} method returns the current Spring AOP proxy. + *

Note: The {@code getThis()} method returns the current Spring AOP proxy. * The {@code getTarget()} method returns the current Spring AOP target (which may be - * {@code null} if there is no target), and is a plain POJO without any advice. - * If you want to call the object and have the advice take effect, use - * {@code getThis()}. A common example is casting the object to an - * introduced interface in the implementation of an introduction. - * - *

Of course there is no such distinction between target and proxy in AspectJ. + * {@code null} if there is no target instance) as a plain POJO without any advice. + * If you want to call the object and have the advice take effect, use {@code getThis()}. + * A common example is casting the object to an introduced interface in the implementation of + * an introduction. There is no such distinction between target and proxy in AspectJ itself. * * @author Rod Johnson * @author Juergen Hoeller @@ -56,7 +54,7 @@ public class MethodInvocationProceedingJoinPoint implements ProceedingJoinPoint, private final ProxyMethodInvocation methodInvocation; - private Object[] defensiveCopyOfArgs; + private Object[] args; /** Lazily initialized signature object */ private Signature signature; @@ -75,6 +73,7 @@ public MethodInvocationProceedingJoinPoint(ProxyMethodInvocation methodInvocatio this.methodInvocation = methodInvocation; } + @Override public void set$AroundClosure(AroundClosure aroundClosure) { throw new UnsupportedOperationException(); @@ -115,12 +114,10 @@ public Object getTarget() { @Override public Object[] getArgs() { - if (this.defensiveCopyOfArgs == null) { - Object[] argsSource = this.methodInvocation.getArguments(); - this.defensiveCopyOfArgs = new Object[argsSource.length]; - System.arraycopy(argsSource, 0, this.defensiveCopyOfArgs, 0, argsSource.length); + if (this.args == null) { + this.args = this.methodInvocation.getArguments().clone(); } - return this.defensiveCopyOfArgs; + return this.args; } @Override @@ -128,7 +125,7 @@ public Signature getSignature() { if (this.signature == null) { this.signature = new MethodSignatureImpl(); } - return signature; + return this.signature; } @Override diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/SimpleAspectInstanceFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/SimpleAspectInstanceFactory.java index 5d413bd697c7..ce243379e1c1 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/SimpleAspectInstanceFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/SimpleAspectInstanceFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ public SimpleAspectInstanceFactory(Class aspectClass) { this.aspectClass = aspectClass; } + /** * Return the specified aspect class (never {@code null}). */ @@ -48,7 +49,6 @@ public final Class getAspectClass() { return this.aspectClass; } - @Override public final Object getAspectInstance() { try { diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/TypePatternClassFilter.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/TypePatternClassFilter.java index 9d2bc141c6f1..d075741490fd 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/TypePatternClassFilter.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/TypePatternClassFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ * Spring AOP {@link ClassFilter} implementation using AspectJ type matching. * * @author Rod Johnson + * @author Juergen Hoeller * @since 2.0 */ public class TypePatternClassFilter implements ClassFilter { @@ -76,17 +77,21 @@ public TypePatternClassFilter(String typePattern) { * or is recognized as invalid */ public void setTypePattern(String typePattern) { - Assert.notNull(typePattern); + Assert.notNull(typePattern, "Type pattern must not be null"); this.typePattern = typePattern; this.aspectJTypePatternMatcher = PointcutParser.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution(). parseTypePattern(replaceBooleanOperators(typePattern)); } + /** + * Return the AspectJ type pattern to match. + */ public String getTypePattern() { - return typePattern; + return this.typePattern; } + /** * Should the pointcut apply to the given interface or target class? * @param clazz candidate target class @@ -95,9 +100,7 @@ public String getTypePattern() { */ @Override public boolean matches(Class clazz) { - if (this.aspectJTypePatternMatcher == null) { - throw new IllegalStateException("No 'typePattern' has been set via ctor/setter."); - } + Assert.state(this.aspectJTypePatternMatcher != null, "No type pattern has been set"); return this.aspectJTypePatternMatcher.matches(clazz); } @@ -108,9 +111,8 @@ public boolean matches(Class clazz) { *

This method converts back to {@code &&} for the AspectJ pointcut parser. */ private String replaceBooleanOperators(String pcExpr) { - pcExpr = StringUtils.replace(pcExpr," and "," && "); - pcExpr = StringUtils.replace(pcExpr, " or ", " || "); - pcExpr = StringUtils.replace(pcExpr, " not ", " ! "); - return pcExpr; + String result = StringUtils.replace(pcExpr," and "," && "); + result = StringUtils.replace(result, " or ", " || "); + return StringUtils.replace(result, " not ", " ! "); } } 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 98c74f7ad044..84ed46387389 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-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,6 @@ import org.aspectj.lang.reflect.AjTypeSystem; import org.aspectj.lang.reflect.PerClauseKind; -import org.springframework.aop.aspectj.AspectJExpressionPointcut; import org.springframework.aop.framework.AopConfigException; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.annotation.AnnotationUtils; @@ -60,6 +59,9 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac private static final String AJC_MAGIC = "ajc$"; + private static final Class[] ASPECTJ_ANNOTATION_CLASSES = new Class[] { + Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class}; + /** Logger available to subclasses */ protected final Log logger = LogFactory.getLog(getClass()); @@ -121,59 +123,14 @@ public void validate(Class aspectClass) throws AopConfigException { } } - /** - * The pointcut and advice annotations both have an "argNames" member which contains a - * comma-separated list of the argument names. We use this (if non-empty) to build the - * formal parameters for the pointcut. - */ - protected AspectJExpressionPointcut createPointcutExpression( - Method annotatedMethod, Class declarationScope, String[] pointcutParameterNames) { - - Class [] pointcutParameterTypes = new Class[0]; - if (pointcutParameterNames != null) { - pointcutParameterTypes = extractPointcutParameterTypes(pointcutParameterNames,annotatedMethod); - } - - AspectJExpressionPointcut ajexp = - new AspectJExpressionPointcut(declarationScope,pointcutParameterNames,pointcutParameterTypes); - ajexp.setLocation(annotatedMethod.toString()); - return ajexp; - } - - /** - * Create the pointcut parameters needed by aspectj based on the given argument names - * and the argument types that are available from the adviceMethod. Needs to take into - * account (ignore) any JoinPoint based arguments as these are not pointcut context but - * rather part of the advice execution context (thisJoinPoint, thisJoinPointStaticPart) - */ - private Class[] extractPointcutParameterTypes(String[] argNames, Method adviceMethod) { - Class[] ret = new Class[argNames.length]; - Class[] paramTypes = adviceMethod.getParameterTypes(); - if (argNames.length > paramTypes.length) { - throw new IllegalStateException("Expecting at least " + argNames.length + - " arguments in the advice declaration, but only found " + paramTypes.length); - } - - // Make the simplifying assumption for now that all of the JoinPoint based arguments - // come first in the advice declaration. - int typeOffset = paramTypes.length - argNames.length; - for (int i = 0; i < ret.length; i++) { - ret[i] = paramTypes[i + typeOffset]; - } - return ret; - } - - /** * Find and return the first AspectJ annotation on the given method - * (there should only be one anyway...) + * (there should only be one anyway...). */ @SuppressWarnings("unchecked") protected static AspectJAnnotation findAspectJAnnotationOnMethod(Method method) { - Class[] classesToLookFor = new Class[] { - Before.class, Around.class, After.class, AfterReturning.class, AfterThrowing.class, Pointcut.class}; - for (Class c : classesToLookFor) { - AspectJAnnotation foundAnnotation = findAnnotation(method, (Class) c); + for (Class clazz : ASPECTJ_ANNOTATION_CLASSES) { + AspectJAnnotation foundAnnotation = findAnnotation(method, (Class) clazz); if (foundAnnotation != null) { return foundAnnotation; } @@ -192,14 +149,13 @@ private static AspectJAnnotation findAnnotation(Method } + /** + * Enum for AspectJ annotation types. + * @see AspectJAnnotation#getAnnotationType() + */ protected enum AspectJAnnotationType { - AtPointcut, - AtBefore, - AtAfter, - AtAfterReturning, - AtAfterThrowing, - AtAround + AtPointcut, AtAround, AtBefore, AtAfter, AtAfterReturning, AtAfterThrowing } @@ -209,18 +165,18 @@ protected enum AspectJAnnotationType { */ protected static class AspectJAnnotation { - private static final String[] EXPRESSION_PROPERTIES = new String[] {"value", "pointcut"}; + private static final String[] EXPRESSION_ATTRIBUTES = new String[] {"pointcut", "value"}; - private static Map, AspectJAnnotationType> annotationTypes = - new HashMap, AspectJAnnotationType>(); + private static Map, AspectJAnnotationType> annotationTypeMap = + new HashMap, AspectJAnnotationType>(8); static { - annotationTypes.put(Pointcut.class,AspectJAnnotationType.AtPointcut); - annotationTypes.put(After.class,AspectJAnnotationType.AtAfter); - annotationTypes.put(AfterReturning.class,AspectJAnnotationType.AtAfterReturning); - annotationTypes.put(AfterThrowing.class,AspectJAnnotationType.AtAfterThrowing); - annotationTypes.put(Around.class,AspectJAnnotationType.AtAround); - annotationTypes.put(Before.class,AspectJAnnotationType.AtBefore); + annotationTypeMap.put(Pointcut.class, AspectJAnnotationType.AtPointcut); + annotationTypeMap.put(Around.class, AspectJAnnotationType.AtAround); + annotationTypeMap.put(Before.class, AspectJAnnotationType.AtBefore); + annotationTypeMap.put(After.class, AspectJAnnotationType.AtAfter); + annotationTypeMap.put(AfterReturning.class, AspectJAnnotationType.AtAfterReturning); + annotationTypeMap.put(AfterThrowing.class, AspectJAnnotationType.AtAfterThrowing); } private final A annotation; @@ -234,44 +190,31 @@ protected static class AspectJAnnotation { public AspectJAnnotation(A annotation) { this.annotation = annotation; this.annotationType = determineAnnotationType(annotation); - // We know these methods exist with the same name on each object, - // but need to invoke them reflectively as there isn't a common interface. try { this.pointcutExpression = resolveExpression(annotation); - this.argumentNames = (String) annotation.getClass().getMethod("argNames").invoke(annotation); + this.argumentNames = (String) AnnotationUtils.getValue(annotation, "argNames"); } catch (Exception ex) { - throw new IllegalArgumentException(annotation + " cannot be an AspectJ annotation", ex); + throw new IllegalArgumentException(annotation + " is not a valid AspectJ annotation", ex); } } private AspectJAnnotationType determineAnnotationType(A annotation) { - for (Class type : annotationTypes.keySet()) { - if (type.isInstance(annotation)) { - return annotationTypes.get(type); - } + AspectJAnnotationType type = annotationTypeMap.get(annotation.annotationType()); + if (type != null) { + return type; } - throw new IllegalStateException("Unknown annotation type: " + annotation.toString()); + throw new IllegalStateException("Unknown annotation type: " + annotation); } - private String resolveExpression(A annotation) throws Exception { - String expression = null; - for (String methodName : EXPRESSION_PROPERTIES) { - Method method; - try { - method = annotation.getClass().getDeclaredMethod(methodName); - } - catch (NoSuchMethodException ex) { - method = null; - } - if (method != null) { - String candidate = (String) method.invoke(annotation); - if (StringUtils.hasText(candidate)) { - expression = candidate; - } + private String resolveExpression(A annotation) { + for (String attributeName : EXPRESSION_ATTRIBUTES) { + String candidate = (String) AnnotationUtils.getValue(annotation, attributeName); + if (StringUtils.hasText(candidate)) { + return candidate; } } - return expression; + throw new IllegalStateException("Failed to resolve expression: " + annotation); } public AspectJAnnotationType getAnnotationType() { @@ -312,11 +255,11 @@ public String[] getParameterNames(Method method) { if (annotation == null) { return null; } - StringTokenizer strTok = new StringTokenizer(annotation.getArgumentNames(), ","); - if (strTok.countTokens() > 0) { - String[] names = new String[strTok.countTokens()]; + StringTokenizer nameTokens = new StringTokenizer(annotation.getArgumentNames(), ","); + if (nameTokens.countTokens() > 0) { + String[] names = new String[nameTokens.countTokens()]; for (int i = 0; i < names.length; i++) { - names[i] = strTok.nextToken(); + names[i] = nameTokens.nextToken(); } return names; } diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AnnotationAwareAspectJAutoProxyCreator.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AnnotationAwareAspectJAutoProxyCreator.java index dce613ef5df4..19e8dc31a6c7 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AnnotationAwareAspectJAutoProxyCreator.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AnnotationAwareAspectJAutoProxyCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,7 +50,7 @@ public class AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorA private List includePatterns; - private AspectJAdvisorFactory aspectJAdvisorFactory = new ReflectiveAspectJAdvisorFactory(); + private AspectJAdvisorFactory aspectJAdvisorFactory; private BeanFactoryAspectJAdvisorsBuilder aspectJAdvisorsBuilder; @@ -74,6 +74,9 @@ public void setAspectJAdvisorFactory(AspectJAdvisorFactory aspectJAdvisorFactory @Override protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) { super.initBeanFactory(beanFactory); + if (this.aspectJAdvisorFactory == null) { + this.aspectJAdvisorFactory = new ReflectiveAspectJAdvisorFactory(beanFactory); + } this.aspectJAdvisorsBuilder = new BeanFactoryAspectJAdvisorsBuilderAdapter(beanFactory, this.aspectJAdvisorFactory); } @@ -130,6 +133,7 @@ private class BeanFactoryAspectJAdvisorsBuilderAdapter extends BeanFactoryAspect public BeanFactoryAspectJAdvisorsBuilderAdapter( ListableBeanFactory beanFactory, AspectJAdvisorFactory advisorFactory) { + super(beanFactory, advisorFactory); } diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJProxyFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJProxyFactory.java index 4431dad05028..edf1cd357124 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJProxyFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJProxyFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,9 @@ package org.springframework.aop.aspectj.annotation; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.aspectj.lang.reflect.PerClauseKind; @@ -50,7 +50,7 @@ public class AspectJProxyFactory extends ProxyCreatorSupport { /** Cache for singleton aspect instances */ - private static final Map, Object> aspectCache = new HashMap, Object>(); + private static final Map, Object> aspectCache = new ConcurrentHashMap, Object>(); private final AspectJAdvisorFactory aspectFactory = new ReflectiveAspectJAdvisorFactory(); @@ -144,7 +144,7 @@ private AspectMetadata createAspectMetadata(Class aspectClass, String aspectN private MetadataAwareAspectInstanceFactory createAspectInstanceFactory( AspectMetadata am, Class aspectClass, String aspectName) { - MetadataAwareAspectInstanceFactory instanceFactory = null; + MetadataAwareAspectInstanceFactory instanceFactory; if (am.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) { // Create a shared aspect instance. Object instance = getSingletonAspectInstance(aspectClass); @@ -162,23 +162,29 @@ private MetadataAwareAspectInstanceFactory createAspectInstanceFactory( * is created if one cannot be found in the instance cache. */ private Object getSingletonAspectInstance(Class aspectClass) { - synchronized (aspectCache) { - Object instance = aspectCache.get(aspectClass); - if (instance != null) { - return instance; - } - try { - instance = aspectClass.newInstance(); - aspectCache.put(aspectClass, instance); - return instance; - } - catch (InstantiationException ex) { - throw new AopConfigException("Unable to instantiate aspect class [" + aspectClass.getName() + "]", ex); - } - catch (IllegalAccessException ex) { - throw new AopConfigException("Cannot access aspect class [" + aspectClass.getName() + "]", ex); + // Quick check without a lock... + Object instance = aspectCache.get(aspectClass); + if (instance == null) { + synchronized (aspectCache) { + // To be safe, check within full lock now... + instance = aspectCache.get(aspectClass); + if (instance == null) { + try { + instance = aspectClass.newInstance(); + aspectCache.put(aspectClass, instance); + } + catch (InstantiationException ex) { + throw new AopConfigException( + "Unable to instantiate aspect class: " + aspectClass.getName(), ex); + } + catch (IllegalAccessException ex) { + throw new AopConfigException( + "Could not access aspect constructor: " + aspectClass.getName(), ex); + } + } } } + return instance; } diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectMetadata.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectMetadata.java index dd8823ec1cd7..937690c7be3a 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectMetadata.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,9 +35,8 @@ * Metadata for an AspectJ aspect class, with an additional Spring AOP pointcut * for the per clause. * - *

Uses AspectJ 5 AJType reflection API, so is only supported on Java 5. - * Enables us to work with different AspectJ instantiation models such as - * "singleton", "pertarget" and "perthis". + *

Uses AspectJ 5 AJType reflection API, enabling us to work with different + * AspectJ instantiation models such as "singleton", "pertarget" and "perthis". * * @author Rod Johnson * @author Juergen Hoeller @@ -102,20 +101,22 @@ public AspectMetadata(Class aspectClass, String aspectName) { this.ajType = ajType; switch (this.ajType.getPerClause().getKind()) { - case SINGLETON : + case SINGLETON: this.perClausePointcut = Pointcut.TRUE; return; - case PERTARGET : case PERTHIS : + case PERTARGET: + case PERTHIS: AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut(); - ajexp.setLocation("@Aspect annotation on " + aspectClass.getName()); + ajexp.setLocation(aspectClass.getName()); ajexp.setExpression(findPerClause(aspectClass)); + ajexp.setPointcutDeclarationScope(aspectClass); this.perClausePointcut = ajexp; return; - case PERTYPEWITHIN : + case PERTYPEWITHIN: // Works with a type pattern this.perClausePointcut = new ComposablePointcut(new TypePatternClassFilter(findPerClause(aspectClass))); return; - default : + default: throw new AopConfigException( "PerClause " + ajType.getPerClause().getKind() + " not supported by Spring AOP for " + aspectClass); } @@ -125,10 +126,8 @@ public AspectMetadata(Class aspectClass, String aspectName) { * Extract contents from String of form {@code pertarget(contents)}. */ private String findPerClause(Class aspectClass) { - // TODO when AspectJ provides this, we can remove this hack. Hence we don't - // bother to make it elegant. Or efficient. Or robust :-) String str = aspectClass.getAnnotation(Aspect.class).value(); - str = str.substring(str.indexOf("(") + 1); + str = str.substring(str.indexOf('(') + 1); str = str.substring(0, str.length() - 1); return str; } @@ -149,7 +148,7 @@ public Class getAspectClass() { } /** - * Return the aspect class. + * Return the aspect name. */ public String getAspectName() { return this.aspectName; diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java index f68fea5627ea..afb8ceb33291 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java @@ -97,8 +97,19 @@ public AspectMetadata getAspectMetadata() { @Override public Object getAspectCreationMutex() { - return (this.beanFactory instanceof ConfigurableBeanFactory ? - ((ConfigurableBeanFactory) this.beanFactory).getSingletonMutex() : this); + if (this.beanFactory != null) { + if (this.beanFactory.isSingleton(name)) { + // Rely on singleton semantics provided by the factory -> no local lock. + return null; + } + else if (this.beanFactory instanceof ConfigurableBeanFactory) { + // No singleton guarantees from the factory -> let's lock locally but + // reuse the factory's singleton lock, just in case a lazy dependency + // of our advice bean happens to trigger the singleton lock implicitly... + return ((ConfigurableBeanFactory) this.beanFactory).getSingletonMutex(); + } + } + return this; } /** diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectJAdvisorsBuilder.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectJAdvisorsBuilder.java index 5c9a7e7448b3..310ebca4599b 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectJAdvisorsBuilder.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectJAdvisorsBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,10 @@ package org.springframework.aop.aspectj.annotation; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.aspectj.lang.reflect.PerClauseKind; @@ -43,12 +43,12 @@ public class BeanFactoryAspectJAdvisorsBuilder { private final AspectJAdvisorFactory advisorFactory; - private List aspectBeanNames; + private volatile List aspectBeanNames; - private final Map> advisorsCache = new HashMap>(); + private final Map> advisorsCache = new ConcurrentHashMap>(); private final Map aspectFactoryCache = - new HashMap(); + new ConcurrentHashMap(); /** @@ -56,7 +56,7 @@ public class BeanFactoryAspectJAdvisorsBuilder { * @param beanFactory the ListableBeanFactory to scan */ public BeanFactoryAspectJAdvisorsBuilder(ListableBeanFactory beanFactory) { - this(beanFactory, new ReflectiveAspectJAdvisorFactory()); + this(beanFactory, new ReflectiveAspectJAdvisorFactory(beanFactory)); } /** @@ -80,56 +80,57 @@ public BeanFactoryAspectJAdvisorsBuilder(ListableBeanFactory beanFactory, Aspect * @see #isEligibleBean */ public List buildAspectJAdvisors() { - List aspectNames = null; - - synchronized (this) { - aspectNames = this.aspectBeanNames; - if (aspectNames == null) { - List advisors = new LinkedList(); - aspectNames = new LinkedList(); - String[] beanNames = - BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false); - for (String beanName : beanNames) { - if (!isEligibleBean(beanName)) { - continue; - } - // We must be careful not to instantiate beans eagerly as in this - // case they would be cached by the Spring container but would not - // have been weaved - Class beanType = this.beanFactory.getType(beanName); - if (beanType == null) { - continue; - } - if (this.advisorFactory.isAspect(beanType)) { - aspectNames.add(beanName); - AspectMetadata amd = new AspectMetadata(beanType, beanName); - if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) { - MetadataAwareAspectInstanceFactory factory = - new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName); - List classAdvisors = this.advisorFactory.getAdvisors(factory); - if (this.beanFactory.isSingleton(beanName)) { - this.advisorsCache.put(beanName, classAdvisors); + List aspectNames = this.aspectBeanNames; + + if (aspectNames == null) { + synchronized (this) { + aspectNames = this.aspectBeanNames; + if (aspectNames == null) { + List advisors = new LinkedList(); + aspectNames = new LinkedList(); + String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( + this.beanFactory, Object.class, true, false); + for (String beanName : beanNames) { + if (!isEligibleBean(beanName)) { + continue; + } + // We must be careful not to instantiate beans eagerly as in this case they + // would be cached by the Spring container but would not have been weaved. + Class beanType = this.beanFactory.getType(beanName); + if (beanType == null) { + continue; + } + if (this.advisorFactory.isAspect(beanType)) { + aspectNames.add(beanName); + AspectMetadata amd = new AspectMetadata(beanType, beanName); + if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) { + MetadataAwareAspectInstanceFactory factory = + new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName); + List classAdvisors = this.advisorFactory.getAdvisors(factory); + if (this.beanFactory.isSingleton(beanName)) { + this.advisorsCache.put(beanName, classAdvisors); + } + else { + this.aspectFactoryCache.put(beanName, factory); + } + advisors.addAll(classAdvisors); } else { + // Per target or per this. + if (this.beanFactory.isSingleton(beanName)) { + throw new IllegalArgumentException("Bean with name '" + beanName + + "' is a singleton, but aspect instantiation model is not singleton"); + } + MetadataAwareAspectInstanceFactory factory = + new PrototypeAspectInstanceFactory(this.beanFactory, beanName); this.aspectFactoryCache.put(beanName, factory); + advisors.addAll(this.advisorFactory.getAdvisors(factory)); } - advisors.addAll(classAdvisors); - } - else { - // Per target or per this. - if (this.beanFactory.isSingleton(beanName)) { - throw new IllegalArgumentException("Bean with name '" + beanName + - "' is a singleton, but aspect instantiation model is not singleton"); - } - MetadataAwareAspectInstanceFactory factory = - new PrototypeAspectInstanceFactory(this.beanFactory, beanName); - this.aspectFactoryCache.put(beanName, factory); - advisors.addAll(this.advisorFactory.getAdvisors(factory)); } } + this.aspectBeanNames = aspectNames; + return advisors; } - this.aspectBeanNames = aspectNames; - return advisors; } } diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/InstantiationModelAwarePointcutAdvisorImpl.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/InstantiationModelAwarePointcutAdvisorImpl.java index 27d3a9ba43bd..ec36fbe9cd8c 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/InstantiationModelAwarePointcutAdvisorImpl.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/InstantiationModelAwarePointcutAdvisorImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -109,29 +109,22 @@ public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut decl /** - * The pointcut for Spring AOP to use. Actual behaviour of the pointcut will change - * depending on the state of the advice. + * The pointcut for Spring AOP to use. + * Actual behaviour of the pointcut will change depending on the state of the advice. */ @Override public Pointcut getPointcut() { return this.pointcut; } - /** - * This is only of interest for Spring AOP: AspectJ instantiation semantics - * are much richer. In AspectJ terminology, all a return of {@code true} - * means here is that the aspect is not a SINGLETON. - */ @Override - public boolean isPerInstance() { - return (getAspectMetadata().getAjType().getPerClause().getKind() != PerClauseKind.SINGLETON); + public boolean isLazy() { + return this.lazy; } - /** - * Return the AspectJ AspectMetadata for this advisor. - */ - public AspectMetadata getAspectMetadata() { - return this.aspectInstanceFactory.getAspectMetadata(); + @Override + public synchronized boolean isAdviceInstantiated() { + return (this.instantiatedAdvice != null); } /** @@ -145,20 +138,26 @@ public synchronized Advice getAdvice() { return this.instantiatedAdvice; } - @Override - public boolean isLazy() { - return this.lazy; + private Advice instantiateAdvice(AspectJExpressionPointcut pcut) { + return this.aspectJAdvisorFactory.getAdvice(this.aspectJAdviceMethod, pcut, + this.aspectInstanceFactory, this.declarationOrder, this.aspectName); } + /** + * This is only of interest for Spring AOP: AspectJ instantiation semantics + * are much richer. In AspectJ terminology, all a return of {@code true} + * means here is that the aspect is not a SINGLETON. + */ @Override - public synchronized boolean isAdviceInstantiated() { - return (this.instantiatedAdvice != null); + public boolean isPerInstance() { + return (getAspectMetadata().getAjType().getPerClause().getKind() != PerClauseKind.SINGLETON); } - - private Advice instantiateAdvice(AspectJExpressionPointcut pcut) { - return this.aspectJAdvisorFactory.getAdvice(this.aspectJAdviceMethod, pcut, - this.aspectInstanceFactory, this.declarationOrder, this.aspectName); + /** + * Return the AspectJ AspectMetadata for this advisor. + */ + public AspectMetadata getAspectMetadata() { + return this.aspectInstanceFactory.getAspectMetadata(); } public MetadataAwareAspectInstanceFactory getAspectInstanceFactory() { @@ -213,33 +212,26 @@ private void determineAdviceType() { } else { switch (aspectJAnnotation.getAnnotationType()) { - case AtAfter: - case AtAfterReturning: - case AtAfterThrowing: - this.isAfterAdvice = true; - this.isBeforeAdvice = false; - break; - case AtAround: case AtPointcut: - this.isAfterAdvice = false; + case AtAround: this.isBeforeAdvice = false; + this.isAfterAdvice = false; break; case AtBefore: - this.isAfterAdvice = false; this.isBeforeAdvice = true; + this.isAfterAdvice = false; + break; + case AtAfter: + case AtAfterReturning: + case AtAfterThrowing: + this.isBeforeAdvice = false; + this.isAfterAdvice = true; + break; } } } - @Override - public String toString() { - return "InstantiationModelAwarePointcutAdvisor: expression [" + getDeclaredPointcut().getExpression() + - "]; advice method [" + this.aspectJAdviceMethod + "]; perClauseKind=" + - this.aspectInstanceFactory.getAspectMetadata().getAjType().getPerClause().getKind(); - - } - private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException { inputStream.defaultReadObject(); try { @@ -250,11 +242,18 @@ private void readObject(ObjectInputStream inputStream) throws IOException, Class } } + @Override + public String toString() { + return "InstantiationModelAwarePointcutAdvisor: expression [" + getDeclaredPointcut().getExpression() + + "]; advice method [" + this.aspectJAdviceMethod + "]; perClauseKind=" + + this.aspectInstanceFactory.getAspectMetadata().getAjType().getPerClause().getKind(); + } + /** * Pointcut implementation that changes its behaviour when the advice is instantiated. - * Note that this is a dynamic pointcut. Otherwise it might - * be optimized out if it does not at first match statically. + * Note that this is a dynamic pointcut; otherwise it might be optimized out + * if it does not at first match statically. */ private class PerTargetInstantiationModelPointcut extends DynamicMethodMatcherPointcut { @@ -264,8 +263,9 @@ private class PerTargetInstantiationModelPointcut extends DynamicMethodMatcherPo private LazySingletonAspectInstanceFactoryDecorator aspectInstanceFactory; - private PerTargetInstantiationModelPointcut(AspectJExpressionPointcut declaredPointcut, + public PerTargetInstantiationModelPointcut(AspectJExpressionPointcut declaredPointcut, Pointcut preInstantiationPointcut, MetadataAwareAspectInstanceFactory aspectInstanceFactory) { + this.declaredPointcut = declaredPointcut; this.preInstantiationPointcut = preInstantiationPointcut; if (aspectInstanceFactory instanceof LazySingletonAspectInstanceFactoryDecorator) { @@ -275,7 +275,8 @@ private PerTargetInstantiationModelPointcut(AspectJExpressionPointcut declaredPo @Override public boolean matches(Method method, Class targetClass) { - // We're either instantiated and matching on declared pointcut, or uninstantiated matching on either pointcut + // We're either instantiated and matching on declared pointcut, + // or uninstantiated matching on either pointcut... return (isAspectMaterialized() && this.declaredPointcut.matches(method, targetClass)) || this.preInstantiationPointcut.getMethodMatcher().matches(method, targetClass); } diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/LazySingletonAspectInstanceFactoryDecorator.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/LazySingletonAspectInstanceFactoryDecorator.java index 3e76fb47a1e8..18bf9f327b8b 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/LazySingletonAspectInstanceFactoryDecorator.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/LazySingletonAspectInstanceFactoryDecorator.java @@ -48,9 +48,15 @@ public LazySingletonAspectInstanceFactoryDecorator(MetadataAwareAspectInstanceFa @Override public Object getAspectInstance() { if (this.materialized == null) { - synchronized (this.maaif.getAspectCreationMutex()) { - if (this.materialized == null) { - this.materialized = this.maaif.getAspectInstance(); + Object mutex = this.maaif.getAspectCreationMutex(); + if (mutex == null) { + this.materialized = this.maaif.getAspectInstance(); + } + else { + synchronized (mutex) { + if (this.materialized == null) { + this.materialized = this.maaif.getAspectInstance(); + } } } } diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/MetadataAwareAspectInstanceFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/MetadataAwareAspectInstanceFactory.java index a001efbc3300..5f89f007c12c 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/MetadataAwareAspectInstanceFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/MetadataAwareAspectInstanceFactory.java @@ -41,7 +41,7 @@ public interface MetadataAwareAspectInstanceFactory extends AspectInstanceFactor /** * Return the best possible creation mutex for this factory. - * @return the mutex object (never {@code null}) + * @return the mutex object (may be {@code null} for no mutex to use) * @since 4.3 */ Object getAspectCreationMutex(); 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 ce139f710df9..c8504c834cd0 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,9 +20,9 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.LinkedList; import java.util.List; import org.aopalliance.aop.Advice; @@ -46,6 +46,7 @@ import org.springframework.aop.aspectj.DeclareParentsAdvisor; import org.springframework.aop.framework.AopConfigException; import org.springframework.aop.support.DefaultPointcutAdvisor; +import org.springframework.beans.factory.BeanFactory; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConvertingComparator; @@ -95,6 +96,30 @@ public String convert(Method method) { } + private final BeanFactory beanFactory; + + + /** + * Create a new {@code ReflectiveAspectJAdvisorFactory}. + */ + public ReflectiveAspectJAdvisorFactory() { + this(null); + } + + /** + * Create a new {@code ReflectiveAspectJAdvisorFactory}, propagating the given + * {@link BeanFactory} to the created {@link AspectJExpressionPointcut} instances, + * for bean pointcut handling as well as consistent {@link ClassLoader} resolution. + * @param beanFactory the BeanFactory to propagate (may be {@code null}} + * @since 4.3.6 + * @see AspectJExpressionPointcut#setBeanFactory + * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#getBeanClassLoader() + */ + public ReflectiveAspectJAdvisorFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override public List getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) { Class aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass(); @@ -106,7 +131,7 @@ public List getAdvisors(MetadataAwareAspectInstanceFactory aspectInstan MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory = new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory); - List advisors = new LinkedList(); + List advisors = new ArrayList(); for (Method method : getAdvisorMethods(aspectClass)) { Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName); if (advisor != null) { @@ -132,7 +157,7 @@ public List getAdvisors(MetadataAwareAspectInstanceFactory aspectInstan } private List getAdvisorMethods(Class aspectClass) { - final List methods = new LinkedList(); + final List methods = new ArrayList(); ReflectionUtils.doWithMethods(aspectClass, new ReflectionUtils.MethodCallback() { @Override public void doWith(Method method) throws IllegalArgumentException { @@ -151,7 +176,7 @@ public void doWith(Method method) throws IllegalArgumentException { * for the given introduction field. *

Resulting Advisors will need to be evaluated for targets. * @param introductionField the field to introspect - * @return {@code null} if not an Advisor + * @return the Advisor instance, or {@code null} if not an Advisor */ private Advisor getDeclareParentsAdvisor(Field introductionField) { DeclareParents declareParents = introductionField.getAnnotation(DeclareParents.class); @@ -161,9 +186,7 @@ private Advisor getDeclareParentsAdvisor(Field introductionField) { } if (DeclareParents.class == declareParents.defaultImpl()) { - // This is what comes back if it wasn't set. This seems bizarre... - // TODO this restriction possibly should be relaxed - throw new IllegalStateException("defaultImpl must be set on DeclareParents"); + throw new IllegalStateException("'defaultImpl' attribute must be set on DeclareParents"); } return new DeclareParentsAdvisor( @@ -197,6 +220,7 @@ private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Clas AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class[0]); ajexp.setExpression(aspectJAnnotation.getPointcutExpression()); + ajexp.setBeanFactory(this.beanFactory); return ajexp; } @@ -229,6 +253,15 @@ public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut AbstractAspectJAdvice springAdvice; switch (aspectJAnnotation.getAnnotationType()) { + case AtPointcut: + if (logger.isDebugEnabled()) { + logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'"); + } + return null; + case AtAround: + springAdvice = new AspectJAroundAdvice( + candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); + break; case AtBefore: springAdvice = new AspectJMethodBeforeAdvice( candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); @@ -253,15 +286,6 @@ public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut springAdvice.setThrowingName(afterThrowingAnnotation.throwing()); } break; - case AtAround: - springAdvice = new AspectJAroundAdvice( - candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); - break; - case AtPointcut: - if (logger.isDebugEnabled()) { - logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'"); - } - return null; default: throw new UnsupportedOperationException( "Unsupported advice type on method: " + candidateAdviceMethod); @@ -275,6 +299,7 @@ public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut springAdvice.setArgumentNamesFromStringArray(argNames); } springAdvice.calculateArgumentBindings(); + return springAdvice; } diff --git a/spring-aop/src/main/java/org/springframework/aop/config/AbstractInterceptorDrivenBeanDefinitionDecorator.java b/spring-aop/src/main/java/org/springframework/aop/config/AbstractInterceptorDrivenBeanDefinitionDecorator.java index 65169d1a8d04..6495f25af93b 100644 --- a/spring-aop/src/main/java/org/springframework/aop/config/AbstractInterceptorDrivenBeanDefinitionDecorator.java +++ b/spring-aop/src/main/java/org/springframework/aop/config/AbstractInterceptorDrivenBeanDefinitionDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,7 +71,7 @@ public final BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder defin BeanDefinition interceptorDefinition = createInterceptorDefinition(node); // generate name and register the interceptor - String interceptorName = existingBeanName + "." + getInterceptorNameSuffix(interceptorDefinition); + String interceptorName = existingBeanName + '.' + getInterceptorNameSuffix(interceptorDefinition); BeanDefinitionReaderUtils.registerBeanDefinition( new BeanDefinitionHolder(interceptorDefinition, interceptorName), registry); diff --git a/spring-aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java b/spring-aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java index c2271500600d..72b5c6265393 100644 --- a/spring-aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java +++ b/spring-aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,11 +31,10 @@ /** * Utility class for handling registration of AOP auto-proxy creators. * - *

Only a single auto-proxy creator can be registered yet multiple concrete - * implementations are available. Therefore this class wraps a simple escalation - * protocol, allowing classes to request a particular auto-proxy creator and know - * that class, {@code or a subclass thereof}, will eventually be resident - * in the application context. + *

Only a single auto-proxy creator should be registered yet multiple concrete + * implementations are available. This class provides a simple escalation protocol, + * allowing a caller to request a particular auto-proxy creator and know that creator, + * or a more capable variant thereof, will be registered as a post-processor. * * @author Rob Harrop * @author Juergen Hoeller @@ -54,12 +53,10 @@ public abstract class AopConfigUtils { /** * Stores the auto proxy creator classes in escalation order. */ - private static final List> APC_PRIORITY_LIST = new ArrayList>(); + private static final List> APC_PRIORITY_LIST = new ArrayList>(3); - /** - * Setup the escalation list. - */ static { + // Set up the escalation list... APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class); APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class); APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class); @@ -107,6 +104,7 @@ public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry reg private static BeanDefinition registerOrEscalateApcAsRequired(Class cls, BeanDefinitionRegistry registry, Object source) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); + if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); if (!cls.getName().equals(apcDefinition.getBeanClassName())) { @@ -118,6 +116,7 @@ private static BeanDefinition registerOrEscalateApcAsRequired(Class cls, Bean } return null; } + RootBeanDefinition beanDefinition = new RootBeanDefinition(cls); beanDefinition.setSource(source); beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE); diff --git a/spring-aop/src/main/java/org/springframework/aop/config/AopNamespaceUtils.java b/spring-aop/src/main/java/org/springframework/aop/config/AopNamespaceUtils.java index 8298f9e09919..5c8e7ce94e94 100644 --- a/spring-aop/src/main/java/org/springframework/aop/config/AopNamespaceUtils.java +++ b/spring-aop/src/main/java/org/springframework/aop/config/AopNamespaceUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,11 +27,11 @@ * Utility class for handling registration of auto-proxy creators used internally * by the '{@code aop}' namespace tags. * - *

Only a single auto-proxy creator can be registered and multiple tags may wish - * to register different concrete implementations. As such this class delegates to - * {@link AopConfigUtils} which wraps a simple escalation protocol. Therefore classes - * may request a particular auto-proxy creator and know that class, or a subclass - * thereof, will eventually be resident in the application context. + *

Only a single auto-proxy creator should be registered and multiple configuration + * elements may wish to register different concrete implementations. As such this class + * delegates to {@link AopConfigUtils} which provides a simple escalation protocol. + * Callers may request a particular auto-proxy creator and know that creator, + * or a more capable variant thereof, will be registered as a post-processor. * * @author Rob Harrop * @author Juergen Hoeller @@ -81,11 +81,11 @@ public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary( private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, Element sourceElement) { if (sourceElement != null) { - boolean proxyTargetClass = Boolean.valueOf(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE)); + boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE)); if (proxyTargetClass) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); } - boolean exposeProxy = Boolean.valueOf(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE)); + boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE)); if (exposeProxy) { AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); } @@ -94,9 +94,8 @@ private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, private static void registerComponentIfNecessary(BeanDefinition beanDefinition, ParserContext parserContext) { if (beanDefinition != null) { - BeanComponentDefinition componentDefinition = - new BeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME); - parserContext.registerComponent(componentDefinition); + parserContext.registerComponent( + new BeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME)); } } diff --git a/spring-aop/src/main/java/org/springframework/aop/config/SimpleBeanFactoryAwareAspectInstanceFactory.java b/spring-aop/src/main/java/org/springframework/aop/config/SimpleBeanFactoryAwareAspectInstanceFactory.java index 64cc673bab7b..22881292f60a 100644 --- a/spring-aop/src/main/java/org/springframework/aop/config/SimpleBeanFactoryAwareAspectInstanceFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/config/SimpleBeanFactoryAwareAspectInstanceFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,8 @@ import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.core.Ordered; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; /** * Implementation of {@link AspectInstanceFactory} that locates the aspect from the @@ -50,9 +50,7 @@ public void setAspectBeanName(String aspectBeanName) { @Override public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; - if (!StringUtils.hasText(this.aspectBeanName)) { - throw new IllegalArgumentException("'aspectBeanName' is required"); - } + Assert.notNull(this.aspectBeanName, "'aspectBeanName' is required"); } 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 7f366d560296..47181986bf00 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-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -90,7 +90,7 @@ public Object postProcessAfterInitialization(Object bean, String beanName) { return proxyFactory.getProxy(getProxyClassLoader()); } - // No async proxy needed. + // No proxy needed. return bean; } @@ -155,7 +155,7 @@ protected ProxyFactory prepareProxyFactory(Object bean, String beanName) { * Subclasses may choose to implement this: for example, * to change the interfaces exposed. *

The default implementation is empty. - * @param proxyFactory ProxyFactory that is already configured with + * @param proxyFactory the ProxyFactory that is already configured with * target, advisor and interfaces and will be used to create the proxy * immediately after this method returns * @since 4.2.3 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 40940374d8df..6a5a3cf70049 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -93,7 +92,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised { * List of Advisors. If an Advice is added, it will be wrapped * in an Advisor before being added to this List. */ - private List advisors = new LinkedList(); + private List advisors = new ArrayList(); /** * Array updated on changes to the advisors list, which is easier @@ -234,7 +233,7 @@ public boolean removeInterface(Class intf) { @Override public Class[] getProxiedInterfaces() { - return this.interfaces.toArray(new Class[this.interfaces.size()]); + return ClassUtils.toClassArray(this.interfaces); } @Override @@ -480,7 +479,7 @@ public int countAdvicesOfType(Class adviceClass) { * for the given method, based on this configuration. * @param method the proxied method * @param targetClass the target class - * @return List of MethodInterceptors (may also include InterceptorAndDynamicMethodMatchers) + * @return a List of MethodInterceptors (may also include InterceptorAndDynamicMethodMatchers) */ public List getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) { MethodCacheKey cacheKey = new MethodCacheKey(method); @@ -534,7 +533,7 @@ protected void copyConfigurationFrom(AdvisedSupport other, TargetSource targetSo /** * Build a configuration-only copy of this AdvisedSupport, - * replacing the TargetSource + * replacing the TargetSource. */ AdvisedSupport getConfigurationOnlyCopy() { AdvisedSupport copy = new AdvisedSupport(); 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 15535a5e2704..7c611bad3130 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-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,6 +43,25 @@ */ public abstract class AopProxyUtils { + /** + * Obtain the singleton target object behind the given proxy, if any. + * @param candidate the (potential) proxy to check + * @return the singleton target object managed in a {@link SingletonTargetSource}, + * or {@code null} in any other case (not a proxy, not an existing singleton target) + * @since 4.3.8 + * @see Advised#getTargetSource() + * @see SingletonTargetSource#getTarget() + */ + public static Object getSingletonTarget(Object candidate) { + if (candidate instanceof Advised) { + TargetSource targetSource = ((Advised) candidate).getTargetSource(); + if (targetSource instanceof SingletonTargetSource) { + return ((SingletonTargetSource) targetSource).getTarget(); + } + } + return null; + } + /** * Determine the ultimate target class of the given bean instance, traversing * not only a top-level proxy but any number of nested proxies as well — @@ -59,14 +78,7 @@ public static Class ultimateTargetClass(Object candidate) { Class result = null; while (current instanceof TargetClassAware) { result = ((TargetClassAware) current).getTargetClass(); - Object nested = null; - if (current instanceof Advised) { - TargetSource targetSource = ((Advised) current).getTargetSource(); - if (targetSource instanceof SingletonTargetSource) { - nested = ((SingletonTargetSource) targetSource).getTarget(); - } - } - current = nested; + current = getSingletonTarget(current); } if (result == null) { result = (AopUtils.isCglibProxy(candidate) ? candidate.getClass().getSuperclass() : candidate.getClass()); 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 7e60723e0311..2bb00f652639 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-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.WeakHashMap; import org.aopalliance.aop.Advice; @@ -56,9 +57,6 @@ /** * CGLIB-based {@link AopProxy} implementation for the Spring AOP framework. * - *

Formerly named {@code Cglib2AopProxy}, as of Spring 3.2, this class depends on - * Spring's own internally repackaged version of CGLIB 3.. - * *

Objects of this type should be obtained through proxy factories, * configured by an {@link AdvisedSupport} object. This class is internal * to Spring's AOP framework and need not be used directly by client code. @@ -203,18 +201,16 @@ public Object getProxy(ClassLoader classLoader) { return createProxyClassAndInstance(enhancer, callbacks); } catch (CodeGenerationException ex) { - throw new AopConfigException("Could not generate CGLIB subclass of class [" + - this.advised.getTargetClass() + "]: " + - "Common causes of this problem include using a final class or a non-visible class", + throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() + + ": Common causes of this problem include using a final class or a non-visible class", ex); } catch (IllegalArgumentException ex) { - throw new AopConfigException("Could not generate CGLIB subclass of class [" + - this.advised.getTargetClass() + "]: " + - "Common causes of this problem include using a final class or a non-visible class", + throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() + + ": Common causes of this problem include using a final class or a non-visible class", ex); } - catch (Exception ex) { + catch (Throwable ex) { // TargetSource.getTarget() failed throw new AopConfigException("Unexpected AOP exception", ex); } @@ -241,10 +237,11 @@ protected Enhancer createEnhancer() { * validates it if not. */ private void validateClassIfNecessary(Class proxySuperClass, ClassLoader proxyClassLoader) { - if (logger.isInfoEnabled()) { + if (logger.isWarnEnabled()) { synchronized (validatedClasses) { if (!validatedClasses.containsKey(proxySuperClass)) { - doValidateClass(proxySuperClass, proxyClassLoader); + doValidateClass(proxySuperClass, proxyClassLoader, + ClassUtils.getAllInterfacesForClassAsSet(proxySuperClass)); validatedClasses.put(proxySuperClass, Boolean.TRUE); } } @@ -255,30 +252,35 @@ private void validateClassIfNecessary(Class proxySuperClass, ClassLoader prox * Checks for final methods on the given {@code Class}, as well as package-visible * methods across ClassLoaders, and writes warnings to the log for each one found. */ - private void doValidateClass(Class proxySuperClass, ClassLoader proxyClassLoader) { - if (Object.class != proxySuperClass) { + private void doValidateClass(Class proxySuperClass, ClassLoader proxyClassLoader, Set> ifcs) { + if (proxySuperClass != Object.class) { Method[] methods = proxySuperClass.getDeclaredMethods(); for (Method method : methods) { int mod = method.getModifiers(); - if (!Modifier.isStatic(mod)) { + if (!Modifier.isStatic(mod) && !Modifier.isPrivate(mod)) { if (Modifier.isFinal(mod)) { - logger.info("Unable to proxy method [" + method + "] because it is final: " + - "All calls to this method via a proxy will NOT be routed to the target instance."); + if (implementsInterface(method, ifcs)) { + logger.warn("Unable to proxy interface-implementing method [" + method + "] because " + + "it is marked as final: Consider using interface-based JDK proxies instead!"); + } + logger.info("Final method [" + method + "] cannot get proxied via CGLIB: " + + "Calls to this method will NOT be routed to the target instance and " + + "might lead to NPEs against uninitialized fields in the proxy instance."); } - else if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod) && !Modifier.isPrivate(mod) && + else if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod) && proxyClassLoader != null && proxySuperClass.getClassLoader() != proxyClassLoader) { - logger.info("Unable to proxy method [" + method + "] because it is package-visible " + - "across different ClassLoaders: All calls to this method via a proxy will " + - "NOT be routed to the target instance."); + logger.info("Method [" + method + "] is package-visible across different ClassLoaders " + + "and cannot get proxied via CGLIB: Declare this method as public or protected " + + "if you need to support invocations through the proxy."); } } } - doValidateClass(proxySuperClass.getSuperclass(), proxyClassLoader); + doValidateClass(proxySuperClass.getSuperclass(), proxyClassLoader, ifcs); } } private Callback[] getCallbacks(Class rootClass) throws Exception { - // Parameters used for optimisation choices... + // Parameters used for optimization choices... boolean exposeProxy = this.advised.isExposeProxy(); boolean isFrozen = this.advised.isFrozen(); boolean isStatic = this.advised.getTargetSource().isStatic(); @@ -290,20 +292,20 @@ private Callback[] getCallbacks(Class rootClass) throws Exception { // unadvised but can return this). May be required to expose the proxy. Callback targetInterceptor; if (exposeProxy) { - targetInterceptor = isStatic ? + targetInterceptor = (isStatic ? new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) : - new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource()); + new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource())); } else { - targetInterceptor = isStatic ? + targetInterceptor = (isStatic ? new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) : - new DynamicUnadvisedInterceptor(this.advised.getTargetSource()); + new DynamicUnadvisedInterceptor(this.advised.getTargetSource())); } // Choose a "direct to target" dispatcher (used for // unadvised calls to static targets that cannot return this). - Callback targetDispatcher = isStatic ? - new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp(); + Callback targetDispatcher = (isStatic ? + new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp()); Callback[] mainCallbacks = new Callback[] { aopInterceptor, // for normal advice @@ -317,14 +319,14 @@ private Callback[] getCallbacks(Class rootClass) throws Exception { Callback[] callbacks; // If the target is a static one and the advice chain is frozen, - // then we can make some optimisations by sending the AOP calls + // then we can make some optimizations by sending the AOP calls // direct to the target using the fixed chain for that method. if (isStatic && isFrozen) { Method[] methods = rootClass.getMethods(); Callback[] fixedCallbacks = new Callback[methods.length]; this.fixedInterceptorMap = new HashMap(methods.length); - // TODO: small memory optimisation here (can skip creation for methods with no advice) + // TODO: small memory optimization here (can skip creation for methods with no advice) for (int x = 0; x < methods.length; x++) { List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(methods[x], rootClass); fixedCallbacks[x] = new FixedChainStaticTargetInterceptor( @@ -345,13 +347,39 @@ private Callback[] getCallbacks(Class rootClass) throws Exception { return callbacks; } + + @Override + public boolean equals(Object other) { + return (this == other || (other instanceof CglibAopProxy && + AopProxyUtils.equalsInProxy(this.advised, ((CglibAopProxy) other).advised))); + } + + @Override + public int hashCode() { + return CglibAopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode(); + } + + + /** + * Check whether the given method is declared on any of the given interfaces. + */ + private static boolean implementsInterface(Method method, Set> ifcs) { + for (Class ifc : ifcs) { + if (ClassUtils.hasMethod(ifc, method.getName(), method.getParameterTypes())) { + return true; + } + } + return false; + } + /** * Process a return value. Wraps a return of {@code this} if necessary to be the * {@code proxy} and also verifies that {@code null} is not returned as a primitive. */ private static Object processReturnType(Object proxy, Object target, Method method, Object retVal) { // Massage return value if necessary - if (retVal != null && retVal == target && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { + if (retVal != null && retVal == target && + !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { // Special case: it returned "this". Note that we can't help // if the target sets a reference to itself in another returned object. retVal = proxy; @@ -365,18 +393,6 @@ private static Object processReturnType(Object proxy, Object target, Method meth } - @Override - public boolean equals(Object other) { - return (this == other || (other instanceof CglibAopProxy && - AopProxyUtils.equalsInProxy(this.advised, ((CglibAopProxy) other).advised))); - } - - @Override - public int hashCode() { - return CglibAopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode(); - } - - /** * Serializable replacement for CGLIB's NoOp interface. * Public to allow use elsewhere in the framework. @@ -794,12 +810,16 @@ public int accept(Method method) { } // We must always proxy equals, to direct calls to this. if (AopUtils.isEqualsMethod(method)) { - logger.debug("Found 'equals' method: " + method); + if (logger.isDebugEnabled()) { + logger.debug("Found 'equals' method: " + method); + } return INVOKE_EQUALS; } // We must always calculate hashCode based on the proxy. if (AopUtils.isHashCodeMethod(method)) { - logger.debug("Found 'hashCode' method: " + method); + if (logger.isDebugEnabled()) { + logger.debug("Found 'hashCode' method: " + method); + } return INVOKE_HASHCODE; } Class targetClass = this.advised.getTargetClass(); @@ -822,51 +842,42 @@ public int accept(Method method) { // Else use the AOP_PROXY. if (isStatic && isFrozen && this.fixedInterceptorMap.containsKey(key)) { if (logger.isDebugEnabled()) { - logger.debug("Method has advice and optimisations are enabled: " + method); + logger.debug("Method has advice and optimizations are enabled: " + method); } - // We know that we are optimising so we can use the FixedStaticChainInterceptors. + // We know that we are optimizing so we can use the FixedStaticChainInterceptors. int index = this.fixedInterceptorMap.get(key); return (index + this.fixedInterceptorOffset); } else { if (logger.isDebugEnabled()) { - logger.debug("Unable to apply any optimisations to advised method: " + method); + logger.debug("Unable to apply any optimizations to advised method: " + method); } return AOP_PROXY; } } else { - // See if the return type of the method is outside the class hierarchy - // of the target type. If so we know it never needs to have return type - // massage and can use a dispatcher. - // If the proxy is being exposed, then must use the interceptor the - // correct one is already configured. If the target is not static, then - // cannot use a dispatcher because the target cannot be released. + // See if the return type of the method is outside the class hierarchy of the target type. + // If so we know it never needs to have return type massage and can use a dispatcher. + // If the proxy is being exposed, then must use the interceptor the correct one is already + // configured. If the target is not static, then we cannot use a dispatcher because the + // target needs to be explicitly released after the invocation. if (exposeProxy || !isStatic) { return INVOKE_TARGET; } Class returnType = method.getReturnType(); - if (targetClass == returnType) { + if (returnType.isAssignableFrom(targetClass)) { if (logger.isDebugEnabled()) { - logger.debug("Method " + method + - "has return type same as target type (may return this) - using INVOKE_TARGET"); + logger.debug("Method return type is assignable from target type and " + + "may therefore return 'this' - using INVOKE_TARGET: " + method); } return INVOKE_TARGET; } - else if (returnType.isPrimitive() || !returnType.isAssignableFrom(targetClass)) { - if (logger.isDebugEnabled()) { - logger.debug("Method " + method + - " has return type that ensures this cannot be returned- using DISPATCH_TARGET"); - } - return DISPATCH_TARGET; - } else { if (logger.isDebugEnabled()) { - logger.debug("Method " + method + - "has return type that is assignable from the target type (may return this) - " + - "using INVOKE_TARGET"); + logger.debug("Method return type ensures 'this' cannot be returned - " + + "using DISPATCH_TARGET: " + method); } - return INVOKE_TARGET; + return DISPATCH_TARGET; } } } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java index 740a7bb8ff24..e1e0cdfb8830 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,9 +62,9 @@ public List getInterceptorsAndDynamicInterceptionAdvice( // Add it conditionally. PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor; if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) { - MethodInterceptor[] interceptors = registry.getInterceptors(advisor); MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher(); if (MethodMatchers.matches(mm, method, actualClass, hasIntroductions)) { + MethodInterceptor[] interceptors = registry.getInterceptors(advisor); if (mm.isRuntime()) { // Creating a new object instance in the getInterceptors() method // isn't a problem as we normally cache created chains. @@ -98,8 +98,7 @@ else if (advisor instanceof IntroductionAdvisor) { * Determine whether the Advisors contain matching introductions. */ private static boolean hasMatchingIntroductions(Advised config, Class actualClass) { - for (int i = 0; i < config.getAdvisors().length; i++) { - Advisor advisor = config.getAdvisors()[i]; + for (Advisor advisor : config.getAdvisors()) { if (advisor instanceof IntroductionAdvisor) { IntroductionAdvisor ia = (IntroductionAdvisor) advisor; if (ia.getClassFilter().matches(actualClass)) { diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java b/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java index 1f90b0b57323..c280830a14fb 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java @@ -215,7 +215,8 @@ else if (!this.advised.opaque && method.getDeclaringClass().isInterface() && // Massage return value if necessary. Class returnType = method.getReturnType(); - if (retVal != null && retVal == target && returnType.isInstance(proxy) && + if (retVal != null && retVal == target && + returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { // Special case: it returned "this" and the return type of the method // is type-compatible. Note that we can't help if the target sets diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyCreatorSupport.java b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyCreatorSupport.java index e1867efd7e3c..7df9a24b80dd 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyCreatorSupport.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyCreatorSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ public class ProxyCreatorSupport extends AdvisedSupport { private AopProxyFactory aopProxyFactory; - private List listeners = new LinkedList(); + private final List listeners = new LinkedList(); /** Set to true when the first AOP proxy has been created */ private boolean active = false; 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 d24c52590944..a56b5dcd2d2d 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-2015 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -640,7 +640,7 @@ public PrototypePlaceholderAdvisor(String beanName) { } public String getBeanName() { - return beanName; + return this.beanName; } @Override diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyProcessorSupport.java b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyProcessorSupport.java index a94c53b10640..74f3073b93f2 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyProcessorSupport.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyProcessorSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.aop.framework; +import java.io.Closeable; + import org.springframework.beans.factory.Aware; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.DisposableBean; @@ -48,9 +50,9 @@ public class ProxyProcessorSupport extends ProxyConfig implements Ordered, BeanC /** - * Set the ordering which will apply to this class's implementation - * of Ordered, used when applying multiple processors. - *

Default value is {@code Integer.MAX_VALUE}, meaning that it's non-ordered. + * Set the ordering which will apply to this processor's implementation + * of {@link Ordered}, used when applying multiple processors. + *

The default value is {@code Ordered.LOWEST_PRECEDENCE}, meaning non-ordered. * @param order the ordering value */ public void setOrder(int order) { @@ -127,6 +129,7 @@ protected void evaluateProxyInterfaces(Class beanClass, ProxyFactory proxyFac */ protected boolean isConfigurationCallbackInterface(Class ifc) { return (InitializingBean.class == ifc || DisposableBean.class == ifc || + Closeable.class == ifc || "java.lang.AutoCloseable".equals(ifc.getName()) || ObjectUtils.containsElement(ifc.getInterfaces(), Aware.class)); } @@ -140,7 +143,8 @@ protected boolean isConfigurationCallbackInterface(Class ifc) { */ protected boolean isInternalLanguageInterface(Class ifc) { return (ifc.getName().equals("groovy.lang.GroovyObject") || - ifc.getName().endsWith(".cglib.proxy.Factory")); + ifc.getName().endsWith(".cglib.proxy.Factory") || + ifc.getName().endsWith(".bytebuddy.MockAccess")); } } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/adapter/AdvisorAdapterRegistry.java b/spring-aop/src/main/java/org/springframework/aop/framework/adapter/AdvisorAdapterRegistry.java index 9eacf479bada..97ff108d831f 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/adapter/AdvisorAdapterRegistry.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/adapter/AdvisorAdapterRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,15 +31,15 @@ public interface AdvisorAdapterRegistry { /** - * Return an Advisor wrapping the given advice. + * Return an {@link Advisor} wrapping the given advice. *

Should by default at least support * {@link org.aopalliance.intercept.MethodInterceptor}, * {@link org.springframework.aop.MethodBeforeAdvice}, * {@link org.springframework.aop.AfterReturningAdvice}, * {@link org.springframework.aop.ThrowsAdvice}. * @param advice object that should be an advice - * @return an Advisor wrapping the given advice. Never returns {@code null}. - * If the advice parameter is an Advisor, return it. + * @return an Advisor wrapping the given advice (never {@code null}; + * if the advice parameter is an Advisor, it is to be returned as-is) * @throws UnknownAdviceTypeException if no registered advisor adapter * can wrap the supposed advice */ @@ -48,21 +48,20 @@ public interface AdvisorAdapterRegistry { /** * Return an array of AOP Alliance MethodInterceptors to allow use of the * given Advisor in an interception-based framework. - *

Don't worry about the pointcut associated with the Advisor, - * if it's a PointcutAdvisor: just return an interceptor. + *

Don't worry about the pointcut associated with the {@link Advisor}, if it is + * a {@link org.springframework.aop.PointcutAdvisor}: just return an interceptor. * @param advisor Advisor to find an interceptor for * @return an array of MethodInterceptors to expose this Advisor's behavior * @throws UnknownAdviceTypeException if the Advisor type is - * not understood by any registered AdvisorAdapter. + * not understood by any registered AdvisorAdapter */ MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException; /** - * Register the given AdvisorAdapter. Note that it is not necessary to register + * Register the given {@link AdvisorAdapter}. Note that it is not necessary to register * adapters for an AOP Alliance Interceptors or Spring Advices: these must be - * automatically recognized by an AdvisorAdapterRegistry implementation. - * @param adapter AdvisorAdapter that understands a particular Advisor - * or Advice types + * automatically recognized by an {@code AdvisorAdapterRegistry} implementation. + * @param adapter AdvisorAdapter that understands particular Advisor or Advice types */ void registerAdvisorAdapter(AdvisorAdapter adapter); diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/adapter/AfterReturningAdviceInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/framework/adapter/AfterReturningAdviceInterceptor.java index 5e0bd1d23d03..82e5856250b6 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/adapter/AfterReturningAdviceInterceptor.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/adapter/AfterReturningAdviceInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,11 +26,13 @@ import org.springframework.util.Assert; /** - * Interceptor to wrap am {@link org.springframework.aop.AfterReturningAdvice}. + * Interceptor to wrap an {@link org.springframework.aop.AfterReturningAdvice}. * Used internally by the AOP framework; application developers should not need * to use this class directly. * * @author Rod Johnson + * @see MethodBeforeAdviceInterceptor + * @see ThrowsAdviceInterceptor */ @SuppressWarnings("serial") public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable { @@ -47,6 +49,7 @@ public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) { this.advice = advice; } + @Override public Object invoke(MethodInvocation mi) throws Throwable { Object retVal = mi.proceed(); diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/adapter/MethodBeforeAdviceInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/framework/adapter/MethodBeforeAdviceInterceptor.java index 8b3fd0ce20ef..a58e27cdc366 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/adapter/MethodBeforeAdviceInterceptor.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/adapter/MethodBeforeAdviceInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.BeforeAdvice; import org.springframework.aop.MethodBeforeAdvice; import org.springframework.util.Assert; @@ -30,11 +31,13 @@ * to use this class directly. * * @author Rod Johnson + * @see AfterReturningAdviceInterceptor + * @see ThrowsAdviceInterceptor */ @SuppressWarnings("serial") -public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable { +public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable { - private MethodBeforeAdvice advice; + private final MethodBeforeAdvice advice; /** @@ -46,9 +49,10 @@ public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) { this.advice = advice; } + @Override public Object invoke(MethodInvocation mi) throws Throwable { - this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() ); + this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis()); return mi.proceed(); } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceInterceptor.java index 5b2ec8f00eeb..e91a8cb8574b 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceInterceptor.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,6 +50,8 @@ * * @author Rod Johnson * @author Juergen Hoeller + * @see MethodBeforeAdviceInterceptor + * @see AfterReturningAdviceInterceptor */ public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice { @@ -66,9 +68,8 @@ public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice { /** * Create a new ThrowsAdviceInterceptor for the given ThrowsAdvice. - * @param throwsAdvice the advice object that defines the exception - * handler methods (usually a {@link org.springframework.aop.ThrowsAdvice} - * implementation) + * @param throwsAdvice the advice object that defines the exception handler methods + * (usually a {@link org.springframework.aop.ThrowsAdvice} implementation) */ public ThrowsAdviceInterceptor(Object throwsAdvice) { Assert.notNull(throwsAdvice, "Advice must not be null"); @@ -76,14 +77,17 @@ public ThrowsAdviceInterceptor(Object throwsAdvice) { Method[] methods = throwsAdvice.getClass().getMethods(); for (Method method : methods) { - if (method.getName().equals(AFTER_THROWING) && - (method.getParameterTypes().length == 1 || method.getParameterTypes().length == 4) && - Throwable.class.isAssignableFrom(method.getParameterTypes()[method.getParameterTypes().length - 1]) - ) { - // Have an exception handler - this.exceptionHandlerMap.put(method.getParameterTypes()[method.getParameterTypes().length - 1], method); - if (logger.isDebugEnabled()) { - logger.debug("Found exception handler method: " + method); + if (method.getName().equals(AFTER_THROWING)) { + Class[] paramTypes = method.getParameterTypes(); + if (paramTypes.length == 1 || paramTypes.length == 4) { + Class throwableParam = paramTypes[paramTypes.length - 1]; + if (Throwable.class.isAssignableFrom(throwableParam)) { + // An exception handler to register... + this.exceptionHandlerMap.put(throwableParam, method); + if (logger.isDebugEnabled()) { + logger.debug("Found exception handler method on throws advice: " + method); + } + } } } } @@ -94,14 +98,33 @@ public ThrowsAdviceInterceptor(Object throwsAdvice) { } } + + /** + * Return the number of handler methods in this advice. + */ public int getHandlerMethodCount() { return this.exceptionHandlerMap.size(); } + + @Override + public Object invoke(MethodInvocation mi) throws Throwable { + try { + return mi.proceed(); + } + catch (Throwable ex) { + Method handlerMethod = getExceptionHandler(ex); + if (handlerMethod != null) { + invokeHandlerMethod(mi, ex, handlerMethod); + } + throw ex; + } + } + /** - * Determine the exception handle method. Can return null if not found. + * Determine the exception handle method for the given exception. * @param exception the exception thrown - * @return a handler for the given exception type + * @return a handler for the given exception type, or {@code null} if none found */ private Method getExceptionHandler(Throwable exception) { Class exceptionClass = exception.getClass(); @@ -119,24 +142,10 @@ private Method getExceptionHandler(Throwable exception) { return handler; } - @Override - public Object invoke(MethodInvocation mi) throws Throwable { - try { - return mi.proceed(); - } - catch (Throwable ex) { - Method handlerMethod = getExceptionHandler(ex); - if (handlerMethod != null) { - invokeHandlerMethod(mi, ex, handlerMethod); - } - throw ex; - } - } - private void invokeHandlerMethod(MethodInvocation mi, Throwable ex, Method method) throws Throwable { Object[] handlerArgs; if (method.getParameterTypes().length == 1) { - handlerArgs = new Object[] { ex }; + handlerArgs = new Object[] {ex}; } else { handlerArgs = new Object[] {mi.getMethod(), mi.getArguments(), mi.getThis(), ex}; diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAdvisorAutoProxyCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAdvisorAutoProxyCreator.java index 92fc174bea69..d7a8d1168b79 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAdvisorAutoProxyCreator.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAdvisorAutoProxyCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,7 +54,8 @@ public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyC public void setBeanFactory(BeanFactory beanFactory) { super.setBeanFactory(beanFactory); if (!(beanFactory instanceof ConfigurableListableBeanFactory)) { - throw new IllegalStateException("Cannot use AdvisorAutoProxyCreator without a ConfigurableListableBeanFactory"); + throw new IllegalArgumentException( + "AdvisorAutoProxyCreator requires a ConfigurableListableBeanFactory: " + beanFactory); } initBeanFactory((ConfigurableListableBeanFactory) beanFactory); } 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 729343083bba..904c76505378 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-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,26 +55,25 @@ * before invoking the bean itself. * *

This class distinguishes between "common" interceptors: shared for all proxies it - * creates, and "specific" interceptors: unique per bean instance. There need not - * be any common interceptors. If there are, they are set using the interceptorNames - * property. As with ProxyFactoryBean, interceptors names in the current factory - * are used rather than bean references to allow correct handling of prototype - * advisors and interceptors: for example, to support stateful mixins. - * Any advice type is supported for "interceptorNames" entries. + * creates, and "specific" interceptors: unique per bean instance. There need not be any + * common interceptors. If there are, they are set using the interceptorNames property. + * As with {@link org.springframework.aop.framework.ProxyFactoryBean}, interceptors names + * in the current factory are used rather than bean references to allow correct handling + * of prototype advisors and interceptors: for example, to support stateful mixins. + * Any advice type is supported for {@link #setInterceptorNames "interceptorNames"} entries. * *

Such auto-proxying is particularly useful if there's a large number of beans that * need to be wrapped with similar proxies, i.e. delegating to the same interceptors. * Instead of x repetitive proxy definitions for x target beans, you can register * one single such post processor with the bean factory to achieve the same effect. * - *

Subclasses can apply any strategy to decide if a bean is to be proxied, - * e.g. by type, by name, by definition details, etc. They can also return - * additional interceptors that should just be applied to the specific bean - * instance. The default concrete implementation is BeanNameAutoProxyCreator, - * identifying the beans to be proxied via a list of bean names. + *

Subclasses can apply any strategy to decide if a bean is to be proxied, e.g. by type, + * by name, by definition details, etc. They can also return additional interceptors that + * should just be applied to the specific bean instance. A simple concrete implementation is + * {@link BeanNameAutoProxyCreator}, identifying the beans to be proxied via given names. * *

Any number of {@link TargetSourceCreator} implementations can be used to create - * a custom target source - for example, to pool prototype objects. Auto-proxying will + * a custom target source: for example, to pool prototype objects. Auto-proxying will * occur even if there is no advice, as long as a TargetSourceCreator specifies a custom * {@link org.springframework.aop.TargetSource}. If there are no TargetSourceCreators set, * or if none matches, a {@link org.springframework.aop.target.SingletonTargetSource} @@ -156,8 +155,8 @@ public boolean isFrozen() { } /** - * Specify the AdvisorAdapterRegistry to use. - * Default is the global AdvisorAdapterRegistry. + * Specify the {@link AdvisorAdapterRegistry} to use. + *

Default is the global {@link AdvisorAdapterRegistry}. * @see org.springframework.aop.framework.adapter.GlobalAdvisorAdapterRegistry */ public void setAdvisorAdapterRegistry(AdvisorAdapterRegistry advisorAdapterRegistry) { @@ -165,18 +164,18 @@ public void setAdvisorAdapterRegistry(AdvisorAdapterRegistry advisorAdapterRegis } /** - * Set custom TargetSourceCreators to be applied in this order. - * If the list is empty, or they all return null, a SingletonTargetSource + * Set custom {@code TargetSourceCreators} to be applied in this order. + * If the list is empty, or they all return null, a {@link SingletonTargetSource} * will be created for each bean. *

Note that TargetSourceCreators will kick in even for target beans - * where no advices or advisors have been found. If a TargetSourceCreator - * returns a TargetSource for a specific bean, that bean will be proxied + * where no advices or advisors have been found. If a {@code TargetSourceCreator} + * returns a {@link TargetSource} for a specific bean, that bean will be proxied * in any case. - *

TargetSourceCreators can only be invoked if this post processor is used - * in a BeanFactory, and its BeanFactoryAware callback is used. - * @param targetSourceCreators list of TargetSourceCreator. - * Ordering is significant: The TargetSource returned from the first matching - * TargetSourceCreator (that is, the first that returns non-null) will be used. + *

{@code TargetSourceCreators} can only be invoked if this post processor is used + * in a {@link BeanFactory} and its {@link BeanFactoryAware} callback is triggered. + * @param targetSourceCreators the list of {@code TargetSourceCreators}. + * Ordering is significant: The {@code TargetSource} returned from the first matching + * {@code TargetSourceCreator} (that is, the first that returns non-null) will be used. */ public void setCustomTargetSourceCreators(TargetSourceCreator... targetSourceCreators) { this.customTargetSourceCreators = targetSourceCreators; @@ -207,8 +206,8 @@ public void setBeanFactory(BeanFactory beanFactory) { } /** - * Return the owning BeanFactory. - * May be {@code null}, as this object doesn't need to belong to a bean factory. + * Return the owning {@link BeanFactory}. + * May be {@code null}, as this post-processor doesn't need to belong to a bean factory. */ protected BeanFactory getBeanFactory() { return this.beanFactory; @@ -413,7 +412,7 @@ protected TargetSource getCustomTargetSource(Class beanClass, String beanName // Found a matching TargetSource. if (logger.isDebugEnabled()) { logger.debug("TargetSourceCreator [" + tsc + - " found custom TargetSource for bean with name '" + beanName + "'"); + "] found custom TargetSource for bean with name '" + beanName + "'"); } return ts; } @@ -455,10 +454,7 @@ protected Object createProxy( } Advisor[] advisors = buildAdvisors(beanName, specificInterceptors); - for (Advisor advisor : advisors) { - proxyFactory.addAdvisor(advisor); - } - + proxyFactory.addAdvisors(advisors); proxyFactory.setTargetSource(targetSource); customizeProxyFactory(proxyFactory); @@ -557,7 +553,7 @@ private Advisor[] resolveInterceptorNames() { * Subclasses may choose to implement this: for example, * to change the interfaces exposed. *

The default implementation is empty. - * @param proxyFactory ProxyFactory that is already configured with + * @param proxyFactory a ProxyFactory that is already configured with * TargetSource and interfaces and will be used to create the proxy * immediately after this method returns */ diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanFactoryAdvisorRetrievalHelper.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanFactoryAdvisorRetrievalHelper.java index c484520b89be..59a2170efd0c 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanFactoryAdvisorRetrievalHelper.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanFactoryAdvisorRetrievalHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ package org.springframework.aop.framework.autoproxy; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import org.apache.commons.logging.Log; @@ -43,7 +43,7 @@ public class BeanFactoryAdvisorRetrievalHelper { private final ConfigurableListableBeanFactory beanFactory; - private String[] cachedAdvisorBeanNames; + private volatile String[] cachedAdvisorBeanNames; /** @@ -64,22 +64,19 @@ public BeanFactoryAdvisorRetrievalHelper(ConfigurableListableBeanFactory beanFac */ public List findAdvisorBeans() { // Determine list of advisor bean names, if not cached already. - String[] advisorNames = null; - synchronized (this) { - advisorNames = this.cachedAdvisorBeanNames; - if (advisorNames == null) { - // Do not initialize FactoryBeans here: We need to leave all regular beans - // uninitialized to let the auto-proxy creator apply to them! - advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( - this.beanFactory, Advisor.class, true, false); - this.cachedAdvisorBeanNames = advisorNames; - } + String[] advisorNames = this.cachedAdvisorBeanNames; + if (advisorNames == null) { + // Do not initialize FactoryBeans here: We need to leave all regular beans + // uninitialized to let the auto-proxy creator apply to them! + advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( + this.beanFactory, Advisor.class, true, false); + this.cachedAdvisorBeanNames = advisorNames; } if (advisorNames.length == 0) { - return new LinkedList(); + return new ArrayList(); } - List advisors = new LinkedList(); + List advisors = new ArrayList(); for (String name : advisorNames) { if (isEligibleBean(name)) { if (this.beanFactory.isCurrentlyInCreation(name)) { diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java index 044c7bbc1168..dccf975db98b 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,15 +19,16 @@ import org.springframework.beans.factory.BeanNameAware; /** - * BeanPostProcessor implementation that creates AOP proxies based on all candidate - * Advisors in the current BeanFactory. This class is completely generic; it contains - * no special code to handle any particular aspects, such as pooling aspects. + * {@code BeanPostProcessor} implementation that creates AOP proxies based on all + * candidate {@code Advisor}s in the current {@code BeanFactory}. This class is + * completely generic; it contains no special code to handle any particular aspects, + * such as pooling aspects. * *

It's possible to filter out advisors - for example, to use multiple post processors - * of this type in the same factory - by setting the {@code usePrefix} property - * to true, in which case only advisors beginning with the DefaultAdvisorAutoProxyCreator's - * bean name followed by a dot (like "aapc.") will be used. This default prefix can be - * changed from the bean name by setting the {@code advisorBeanNamePrefix} property. + * of this type in the same factory - by setting the {@code usePrefix} property to true, + * in which case only advisors beginning with the DefaultAdvisorAutoProxyCreator's bean + * name followed by a dot (like "aapc.") will be used. This default prefix can be changed + * from the bean name by setting the {@code advisorBeanNamePrefix} property. * The separator (.) will also be used in this case. * * @author Rod Johnson @@ -40,22 +41,22 @@ public class DefaultAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCrea public final static String SEPARATOR = "."; - private boolean usePrefix; + private boolean usePrefix = false; private String advisorBeanNamePrefix; /** - * Set whether to exclude advisors with a certain prefix - * in the bean name. + * Set whether to only include advisors with a certain prefix in the bean name. + *

Default is {@code false}, including all beans of type {@code Advisor}. + * @see #setAdvisorBeanNamePrefix */ public void setUsePrefix(boolean usePrefix) { this.usePrefix = usePrefix; } /** - * Return whether to exclude advisors with a certain prefix - * in the bean name. + * Return whether to only include advisors with a certain prefix in the bean name. */ public boolean isUsePrefix() { return this.usePrefix; @@ -89,7 +90,7 @@ public void setBeanName(String name) { /** - * Consider Advisor beans with the specified prefix as eligible, if activated. + * Consider {@code Advisor} beans with the specified prefix as eligible, if activated. * @see #setUsePrefix * @see #setAdvisorBeanNamePrefix */ 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 3735ba06e7ea..083b7aa9ae75 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-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -150,7 +150,7 @@ protected DefaultListableBeanFactory buildInternalBeanFactory(ConfigurableBeanFa // since those are only meant to apply to beans defined in the original factory. for (Iterator it = internalBeanFactory.getBeanPostProcessors().iterator(); it.hasNext();) { if (it.next() instanceof AopInfrastructureBean) { - it.remove(); + it.remove(); // effectively deprecated: use List.removeIf on Java 8+ } } diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AbstractMonitoringInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AbstractMonitoringInterceptor.java index 1bd6a6d5e14b..a742be2cb6e0 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AbstractMonitoringInterceptor.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AbstractMonitoringInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,12 +22,12 @@ /** * Base class for monitoring interceptors, such as performance monitors. - * Provides {@code prefix} and {@code suffix} properties - * that help to classify/group performance monitoring results. + * Provides configurable "prefix and "suffix" properties that help to + * classify/group performance monitoring results. * - *

Subclasses should call the {@code createInvocationTraceName(MethodInvocation)} - * method to create a name for the given trace that includes information about the - * method invocation under trace along with the prefix and suffix added as appropriate. + *

In their {@link #invokeUnderTrace} implementation, subclasses should call the + * {@link #createInvocationTraceName} method to create a name for the given trace, + * including information about the method invocation along with a prefix/suffix. * * @author Rob Harrop * @author Juergen Hoeller diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AbstractTraceInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AbstractTraceInterceptor.java index 00ae0a2d818d..7450df1438be 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AbstractTraceInterceptor.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AbstractTraceInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,6 +58,12 @@ public abstract class AbstractTraceInterceptor implements MethodInterceptor, Ser */ private boolean hideProxyClassNames = false; + /** + * Indicates whether to pass an exception to the logger. + * @see #writeToLog(Log, String, Throwable) + */ + private boolean logExceptionStackTrace = true; + /** * Set whether to use a dynamic logger or a static logger. @@ -98,6 +104,17 @@ public void setHideProxyClassNames(boolean hideProxyClassNames) { this.hideProxyClassNames = hideProxyClassNames; } + /** + * Set whether to pass an exception to the logger, suggesting inclusion + * of its stack trace into the log. Default is "true"; set this to "false" + * in order to reduce the log output to just the trace message (which may + * include the exception class name and exception message, if applicable). + * @since 4.3.10 + */ + public void setLogExceptionStackTrace(boolean logExceptionStackTrace) { + this.logExceptionStackTrace = logExceptionStackTrace; + } + /** * Determines whether or not logging is enabled for the particular {@code MethodInvocation}. @@ -171,6 +188,40 @@ protected boolean isLogEnabled(Log logger) { return logger.isTraceEnabled(); } + /** + * Write the supplied trace message to the supplied {@code Log} instance. + *

To be called by {@link #invokeUnderTrace} for enter/exit messages. + *

Delegates to {@link #writeToLog(Log, String, Throwable)} as the + * ultimate delegate that controls the underlying logger invocation. + * @since 4.3.10 + * @see #writeToLog(Log, String, Throwable) + */ + protected void writeToLog(Log logger, String message) { + writeToLog(logger, message, null); + } + + /** + * Write the supplied trace message and {@link Throwable} to the + * supplied {@code Log} instance. + *

To be called by {@link #invokeUnderTrace} for enter/exit outcomes, + * potentially including an exception. Note that an exception's stack trace + * won't get logged when {@link #setLogExceptionStackTrace} is "false". + *

By default messages are written at {@code TRACE} level. Subclasses + * can override this method to control which level the message is written + * at, typically also overriding {@link #isLogEnabled} accordingly. + * @since 4.3.10 + * @see #setLogExceptionStackTrace + * @see #isLogEnabled + */ + protected void writeToLog(Log logger, String message, Throwable ex) { + if (ex != null && this.logExceptionStackTrace) { + logger.trace(message, ex); + } + else { + logger.trace(message); + } + } + /** * Subclasses must override this method to perform any tracing around the @@ -180,13 +231,15 @@ protected boolean isLogEnabled(Log logger) { *

By default, the passed-in {@code Log} instance will have log level * "trace" enabled. Subclasses do not have to check for this again, unless * they overwrite the {@code isInterceptorEnabled} method to modify - * the default behavior. + * the default behavior, and may delegate to {@code writeToLog} for actual + * messages to be written. * @param logger the {@code Log} to write trace messages to * @return the result of the call to {@code MethodInvocation.proceed()} * @throws Throwable if the call to {@code MethodInvocation.proceed()} * encountered any errors - * @see #isInterceptorEnabled * @see #isLogEnabled + * @see #writeToLog(Log, String) + * @see #writeToLog(Log, String, Throwable) */ protected abstract Object invokeUnderTrace(MethodInvocation invocation, Log logger) throws Throwable; 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 28917436f08f..7c25f696323b 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-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -228,6 +228,7 @@ protected Executor getDefaultExecutor(BeanFactory beanFactory) { return beanFactory.getBean(TaskExecutor.class); } catch (NoUniqueBeanDefinitionException ex) { + logger.debug("Could not find unique TaskExecutor bean", ex); try { return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class); } @@ -241,8 +242,14 @@ protected Executor getDefaultExecutor(BeanFactory beanFactory) { } catch (NoSuchBeanDefinitionException ex) { logger.debug("Could not find default TaskExecutor bean", ex); + try { + return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class); + } + catch (NoSuchBeanDefinitionException ex2) { + logger.info("No task executor bean found for async processing: " + + "no bean of type TaskExecutor and no bean named 'taskExecutor' either"); + } // Giving up -> either using local default executor or none at all... - logger.info("No TaskExecutor bean found for async processing"); } } return null; diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/CustomizableTraceInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/CustomizableTraceInterceptor.java index f8b19cb71fc6..7598f7ad91b4 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/CustomizableTraceInterceptor.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/CustomizableTraceInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -128,20 +128,20 @@ public class CustomizableTraceInterceptor extends AbstractTraceInterceptor { /** * The default message used for writing method entry messages. */ - private static final String DEFAULT_ENTER_MESSAGE = - "Entering method '" + PLACEHOLDER_METHOD_NAME + "' of class [" + PLACEHOLDER_TARGET_CLASS_NAME + "]"; + private static final String DEFAULT_ENTER_MESSAGE = "Entering method '" + + PLACEHOLDER_METHOD_NAME + "' of class [" + PLACEHOLDER_TARGET_CLASS_NAME + "]"; /** * The default message used for writing method exit messages. */ - private static final String DEFAULT_EXIT_MESSAGE = - "Exiting method '" + PLACEHOLDER_METHOD_NAME + "' of class [" + PLACEHOLDER_TARGET_CLASS_NAME + "]"; + private static final String DEFAULT_EXIT_MESSAGE = "Exiting method '" + + PLACEHOLDER_METHOD_NAME + "' of class [" + PLACEHOLDER_TARGET_CLASS_NAME + "]"; /** * The default message used for writing exception messages. */ - private static final String DEFAULT_EXCEPTION_MESSAGE = - "Exception thrown in method '" + PLACEHOLDER_METHOD_NAME + "' of class [" + PLACEHOLDER_TARGET_CLASS_NAME + "]"; + private static final String DEFAULT_EXCEPTION_MESSAGE = "Exception thrown in method '" + + PLACEHOLDER_METHOD_NAME + "' of class [" + PLACEHOLDER_TARGET_CLASS_NAME + "]"; /** * The {@code Pattern} used to match placeholders. @@ -182,14 +182,14 @@ public class CustomizableTraceInterceptor extends AbstractTraceInterceptor { * */ public void setEnterMessage(String enterMessage) throws IllegalArgumentException { - Assert.hasText(enterMessage, "'enterMessage' must not be empty"); + Assert.hasText(enterMessage, "enterMessage must not be empty"); checkForInvalidPlaceholders(enterMessage); Assert.doesNotContain(enterMessage, PLACEHOLDER_RETURN_VALUE, - "enterMessage cannot contain placeholder [" + PLACEHOLDER_RETURN_VALUE + "]"); + "enterMessage cannot contain placeholder " + PLACEHOLDER_RETURN_VALUE); Assert.doesNotContain(enterMessage, PLACEHOLDER_EXCEPTION, - "enterMessage cannot contain placeholder [" + PLACEHOLDER_EXCEPTION + "]"); + "enterMessage cannot contain placeholder " + PLACEHOLDER_EXCEPTION); Assert.doesNotContain(enterMessage, PLACEHOLDER_INVOCATION_TIME, - "enterMessage cannot contain placeholder [" + PLACEHOLDER_INVOCATION_TIME + "]"); + "enterMessage cannot contain placeholder " + PLACEHOLDER_INVOCATION_TIME); this.enterMessage = enterMessage; } @@ -206,10 +206,10 @@ public void setEnterMessage(String enterMessage) throws IllegalArgumentException * */ public void setExitMessage(String exitMessage) { - Assert.hasText(exitMessage, "'exitMessage' must not be empty"); + Assert.hasText(exitMessage, "exitMessage must not be empty"); checkForInvalidPlaceholders(exitMessage); Assert.doesNotContain(exitMessage, PLACEHOLDER_EXCEPTION, - "exitMessage cannot contain placeholder [" + PLACEHOLDER_EXCEPTION + "]"); + "exitMessage cannot contain placeholder" + PLACEHOLDER_EXCEPTION); this.exitMessage = exitMessage; } @@ -225,12 +225,10 @@ public void setExitMessage(String exitMessage) { * */ public void setExceptionMessage(String exceptionMessage) { - Assert.hasText(exceptionMessage, "'exceptionMessage' must not be empty"); + Assert.hasText(exceptionMessage, "exceptionMessage must not be empty"); checkForInvalidPlaceholders(exceptionMessage); Assert.doesNotContain(exceptionMessage, PLACEHOLDER_RETURN_VALUE, - "exceptionMessage cannot contain placeholder [" + PLACEHOLDER_RETURN_VALUE + "]"); - Assert.doesNotContain(exceptionMessage, PLACEHOLDER_INVOCATION_TIME, - "exceptionMessage cannot contain placeholder [" + PLACEHOLDER_INVOCATION_TIME + "]"); + "exceptionMessage cannot contain placeholder " + PLACEHOLDER_RETURN_VALUE); this.exceptionMessage = exceptionMessage; } @@ -246,7 +244,7 @@ public void setExceptionMessage(String exceptionMessage) { */ @Override protected Object invokeUnderTrace(MethodInvocation invocation, Log logger) throws Throwable { - String name = invocation.getMethod().getDeclaringClass().getName() + "." + invocation.getMethod().getName(); + String name = ClassUtils.getQualifiedMethodName(invocation.getMethod()); StopWatch stopWatch = new StopWatch(name); Object returnValue = null; boolean exitThroughException = false; @@ -262,8 +260,8 @@ protected Object invokeUnderTrace(MethodInvocation invocation, Log logger) throw stopWatch.stop(); } exitThroughException = true; - writeToLog(logger, - replacePlaceholders(this.exceptionMessage, invocation, null, ex, stopWatch.getTotalTimeMillis()), ex); + writeToLog(logger, replacePlaceholders( + this.exceptionMessage, invocation, null, ex, stopWatch.getTotalTimeMillis()), ex); throw ex; } finally { @@ -271,35 +269,12 @@ protected Object invokeUnderTrace(MethodInvocation invocation, Log logger) throw if (stopWatch.isRunning()) { stopWatch.stop(); } - writeToLog(logger, - replacePlaceholders(this.exitMessage, invocation, returnValue, null, stopWatch.getTotalTimeMillis())); + writeToLog(logger, replacePlaceholders( + this.exitMessage, invocation, returnValue, null, stopWatch.getTotalTimeMillis())); } } } - /** - * Writes the supplied message to the supplied {@code Log} instance. - * @see #writeToLog(org.apache.commons.logging.Log, String, Throwable) - */ - protected void writeToLog(Log logger, String message) { - writeToLog(logger, message, null); - } - - /** - * Writes the supplied message and {@link Throwable} to the - * supplied {@code Log} instance. By default messages are written - * at {@code TRACE} level. Sub-classes can override this method - * to control which level the message is written at. - */ - protected void writeToLog(Log logger, String message, Throwable ex) { - if (ex != null) { - logger.trace(message, ex); - } - else { - logger.trace(message); - } - } - /** * Replace the placeholders in the given message with the supplied values, * or values derived from those supplied. diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/JamonPerformanceMonitorInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/JamonPerformanceMonitorInterceptor.java index a1955bc291cc..2b71b3cbbd05 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/JamonPerformanceMonitorInterceptor.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/JamonPerformanceMonitorInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -121,7 +121,7 @@ protected Object invokeUnderTrace(MethodInvocation invocation, Log logger) throw finally { monitor.stop(); if (!this.trackAllInvocations || isLogEnabled(logger)) { - logger.trace("JAMon performance statistics for method [" + name + "]:\n" + monitor); + writeToLog(logger, "JAMon performance statistics for method [" + name + "]:\n" + monitor); } } } diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/PerformanceMonitorInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/PerformanceMonitorInterceptor.java index 4362c5de10d3..dd72c00f357c 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/PerformanceMonitorInterceptor.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/PerformanceMonitorInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,7 +63,7 @@ protected Object invokeUnderTrace(MethodInvocation invocation, Log logger) throw } finally { stopWatch.stop(); - logger.trace(stopWatch.shortSummary()); + writeToLog(logger, stopWatch.shortSummary()); } } diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/SimpleAsyncUncaughtExceptionHandler.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/SimpleAsyncUncaughtExceptionHandler.java index dffba79e80e8..e77f84b43575 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/SimpleAsyncUncaughtExceptionHandler.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/SimpleAsyncUncaughtExceptionHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,13 +29,13 @@ */ public class SimpleAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler { - private final Log logger = LogFactory.getLog(SimpleAsyncUncaughtExceptionHandler.class); + private static final Log logger = LogFactory.getLog(SimpleAsyncUncaughtExceptionHandler.class); + @Override public void handleUncaughtException(Throwable ex, Method method, Object... params) { if (logger.isErrorEnabled()) { - logger.error(String.format("Unexpected error occurred invoking async " + - "method '%s'.", method), ex); + logger.error("Unexpected error occurred invoking async method: " + method, ex); } } diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/SimpleTraceInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/SimpleTraceInterceptor.java index e70f502fd10b..8ca8baa8aef5 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/SimpleTraceInterceptor.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/SimpleTraceInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,14 +55,14 @@ public SimpleTraceInterceptor(boolean useDynamicLogger) { @Override protected Object invokeUnderTrace(MethodInvocation invocation, Log logger) throws Throwable { String invocationDescription = getInvocationDescription(invocation); - logger.trace("Entering " + invocationDescription); + writeToLog(logger, "Entering " + invocationDescription); try { Object rval = invocation.proceed(); - logger.trace("Exiting " + invocationDescription); + writeToLog(logger, "Exiting " + invocationDescription); return rval; } catch (Throwable ex) { - logger.trace("Exception thrown in " + invocationDescription, ex); + writeToLog(logger, "Exception thrown in " + invocationDescription, ex); throw ex; } } diff --git a/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyFactoryBean.java b/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyFactoryBean.java index c93d3dfeb324..6c983d2a6667 100644 --- a/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyFactoryBean.java +++ b/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,7 +50,8 @@ * @see #setProxyTargetClass */ @SuppressWarnings("serial") -public class ScopedProxyFactoryBean extends ProxyConfig implements FactoryBean, BeanFactoryAware { +public class ScopedProxyFactoryBean extends ProxyConfig + implements FactoryBean, BeanFactoryAware, AopInfrastructureBean { /** The TargetSource that manages scoping */ private final SimpleBeanTargetSource scopedTargetSource = new SimpleBeanTargetSource(); diff --git a/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyUtils.java b/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyUtils.java index f6213a7d3c50..9db73d062dc5 100644 --- a/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyUtils.java +++ b/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -89,7 +89,7 @@ public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder defini } /** - * Generates the bean name that is used within the scoped proxy to reference the target bean. + * Generate the bean name that is used within the scoped proxy to reference the target bean. * @param originalBeanName the original name of bean * @return the generated bean to be used to reference the target bean */ 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 2c2eff5feb55..5401bb96a797 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 @@ -46,7 +46,7 @@ public abstract class AbstractBeanFactoryPointcutAdvisor extends AbstractPointcu private BeanFactory beanFactory; - private transient Advice advice; + private transient volatile Advice advice; private transient volatile Object adviceMonitor = new Object(); @@ -98,12 +98,28 @@ public void setAdvice(Advice advice) { @Override public Advice getAdvice() { - synchronized (this.adviceMonitor) { - if (this.advice == null && this.adviceBeanName != null) { - Assert.state(this.beanFactory != null, "BeanFactory must be set to resolve 'adviceBeanName'"); - this.advice = this.beanFactory.getBean(this.adviceBeanName, Advice.class); + Advice advice = this.advice; + if (advice != null || this.adviceBeanName == null) { + return advice; + } + + Assert.state(this.beanFactory != null, "BeanFactory must be set to resolve 'adviceBeanName'"); + if (this.beanFactory.isSingleton(this.adviceBeanName)) { + // Rely on singleton semantics provided by the factory. + advice = this.beanFactory.getBean(this.adviceBeanName, Advice.class); + this.advice = advice; + return advice; + } + else { + // No singleton guarantees from the factory -> let's lock locally but + // reuse the factory's singleton lock, just in case a lazy dependency + // of our advice bean happens to trigger the singleton lock implicitly... + synchronized (this.adviceMonitor) { + if (this.advice == null) { + this.advice = this.beanFactory.getBean(this.adviceBeanName, Advice.class); + } + return this.advice; } - return this.advice; } } diff --git a/spring-aop/src/main/java/org/springframework/aop/support/AbstractRegexpMethodPointcut.java b/spring-aop/src/main/java/org/springframework/aop/support/AbstractRegexpMethodPointcut.java index e61041015378..aa666cba09c6 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/AbstractRegexpMethodPointcut.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/AbstractRegexpMethodPointcut.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import java.util.Arrays; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -129,8 +130,9 @@ public String[] getExcludedPatterns() { */ @Override public boolean matches(Method method, Class targetClass) { - return ((targetClass != null && matchesPattern(targetClass.getName() + "." + method.getName())) || - matchesPattern(method.getDeclaringClass().getName() + "." + method.getName())); + return ((targetClass != null && targetClass != method.getDeclaringClass() && + matchesPattern(ClassUtils.getQualifiedMethodName(method, targetClass))) || + matchesPattern(ClassUtils.getQualifiedMethodName(method, method.getDeclaringClass()))); } /** diff --git a/spring-aop/src/main/java/org/springframework/aop/support/ComposablePointcut.java b/spring-aop/src/main/java/org/springframework/aop/support/ComposablePointcut.java index fd78bfd60486..50a67d2ae2f6 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/ComposablePointcut.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/ComposablePointcut.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ import org.springframework.aop.MethodMatcher; import org.springframework.aop.Pointcut; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; /** * Convenient class for building up pointcuts. All methods return @@ -188,21 +187,14 @@ public boolean equals(Object other) { if (!(other instanceof ComposablePointcut)) { return false; } - ComposablePointcut that = (ComposablePointcut) other; - return ObjectUtils.nullSafeEquals(that.classFilter, this.classFilter) && - ObjectUtils.nullSafeEquals(that.methodMatcher, this.methodMatcher); + ComposablePointcut otherPointcut = (ComposablePointcut) other; + return (this.classFilter.equals(otherPointcut.classFilter) && + this.methodMatcher.equals(otherPointcut.methodMatcher)); } @Override public int hashCode() { - int code = 17; - if (this.classFilter != null) { - code = 37 * code + this.classFilter.hashCode(); - } - if (this.methodMatcher != null) { - code = 37 * code + this.methodMatcher.hashCode(); - } - return code; + return this.classFilter.hashCode() * 37 + this.methodMatcher.hashCode(); } @Override 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 1989a6dae41d..47d201cfcd06 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-2015 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,8 +22,6 @@ import org.springframework.aop.ClassFilter; import org.springframework.aop.MethodMatcher; import org.springframework.aop.Pointcut; -import org.springframework.core.ControlFlow; -import org.springframework.core.ControlFlowFactory; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -34,7 +32,7 @@ * * @author Rod Johnson * @author Rob Harrop - * @see org.springframework.core.ControlFlow + * @author Juergen Hoeller */ @SuppressWarnings("serial") public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher, Serializable { @@ -43,7 +41,7 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher private String methodName; - private int evaluations; + private volatile int evaluations; /** @@ -55,11 +53,11 @@ public ControlFlowPointcut(Class clazz) { } /** - * Construct a new pointcut that matches all calls below the - * given method in the given class. If the method name is null, - * matches all control flows below that class. + * Construct a new pointcut that matches all calls below the given method + * in the given class. If no method name is given, matches all control flows + * below the given class. * @param clazz the clazz - * @param methodName the name of the method + * @param methodName the name of the method (may be {@code null}) */ public ControlFlowPointcut(Class clazz, String methodName) { Assert.notNull(clazz, "Class must not be null"); @@ -93,8 +91,14 @@ public boolean isRuntime() { @Override public boolean matches(Method method, Class targetClass, Object... args) { this.evaluations++; - ControlFlow cflow = ControlFlowFactory.createControlFlow(); - return (this.methodName != null ? cflow.under(this.clazz, this.methodName) : cflow.under(this.clazz)); + + for (StackTraceElement element : new Throwable().getStackTrace()) { + if (element.getClassName().equals(this.clazz.getName()) && + (this.methodName == null || element.getMethodName().equals(this.methodName))) { + return true; + } + } + return false; } /** @@ -130,8 +134,7 @@ public boolean equals(Object other) { @Override public int hashCode() { - int code = 17; - code = 37 * code + this.clazz.hashCode(); + int code = this.clazz.hashCode(); if (this.methodName != null) { code = 37 * code + this.methodName.hashCode(); } 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 32af73dc7bc7..17b167010fa4 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-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,7 +45,7 @@ public class DefaultIntroductionAdvisor implements IntroductionAdvisor, ClassFil private final Set> interfaces = new LinkedHashSet>(); - private int order = Integer.MAX_VALUE; + private int order = Ordered.LOWEST_PRECEDENCE; /** @@ -104,7 +104,7 @@ public void addInterface(Class intf) { @Override public Class[] getInterfaces() { - return this.interfaces.toArray(new Class[this.interfaces.size()]); + return ClassUtils.toClassArray(this.interfaces); } @Override @@ -118,7 +118,6 @@ public void validateInterfaces() throws IllegalArgumentException { } } - public void setOrder(int order) { this.order = order; } @@ -128,7 +127,6 @@ public int getOrder() { return this.order; } - @Override public Advice getAdvice() { return this.advice; diff --git a/spring-aop/src/main/java/org/springframework/aop/support/DynamicMethodMatcher.java b/spring-aop/src/main/java/org/springframework/aop/support/DynamicMethodMatcher.java index 78b230d8f026..1f7dc9748415 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/DynamicMethodMatcher.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/DynamicMethodMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,8 @@ /** * Convenient abstract superclass for dynamic method matchers, * which do care about arguments at runtime. + * + * @author Rod Johnson */ public abstract class DynamicMethodMatcher implements MethodMatcher { diff --git a/spring-aop/src/main/java/org/springframework/aop/support/DynamicMethodMatcherPointcut.java b/spring-aop/src/main/java/org/springframework/aop/support/DynamicMethodMatcherPointcut.java index df3963dc8530..56e29cb0bd94 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/DynamicMethodMatcherPointcut.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/DynamicMethodMatcherPointcut.java @@ -24,7 +24,7 @@ * Convenient superclass when we want to force subclasses to * implement MethodMatcher interface, but subclasses * will want to be pointcuts. The getClassFilter() method can - * be overriden to customize ClassFilter behaviour as well. + * be overridden to customize ClassFilter behaviour as well. * * @author Rod Johnson */ diff --git a/spring-aop/src/main/java/org/springframework/aop/support/IntroductionInfoSupport.java b/spring-aop/src/main/java/org/springframework/aop/support/IntroductionInfoSupport.java index b91c89750282..b0153f91bbf1 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/IntroductionInfoSupport.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/IntroductionInfoSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,15 +53,15 @@ public class IntroductionInfoSupport implements IntroductionInfo, Serializable { * due to the delegate implementing it. Call this method to exclude * internal interfaces from being visible at the proxy level. *

Does nothing if the interface is not implemented by the delegate. - * @param intf the interface to suppress + * @param ifc the interface to suppress */ - public void suppressInterface(Class intf) { - this.publishedInterfaces.remove(intf); + public void suppressInterface(Class ifc) { + this.publishedInterfaces.remove(ifc); } @Override public Class[] getInterfaces() { - return this.publishedInterfaces.toArray(new Class[this.publishedInterfaces.size()]); + return ClassUtils.toClassArray(this.publishedInterfaces); } /** diff --git a/spring-aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcut.java b/spring-aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcut.java index 7ef41f0a3fd9..5a59d87f4f64 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcut.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcut.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ /** * Pointcut bean for simple method name matches, as alternative to regexp patterns. - * Does not handle overloaded methods: all methods *with a given name will be eligible. + * Does not handle overloaded methods: all methods with a given name will be eligible. * * @author Juergen Hoeller * @author Rod Johnson diff --git a/spring-aop/src/main/java/org/springframework/aop/support/Pointcuts.java b/spring-aop/src/main/java/org/springframework/aop/support/Pointcuts.java index b17977ba48ee..8a4f15b07583 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/Pointcuts.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/Pointcuts.java @@ -94,7 +94,7 @@ public static boolean matches(Pointcut pointcut, Method method, Class targetC @SuppressWarnings("serial") private static class SetterPointcut extends StaticMethodMatcherPointcut implements Serializable { - public static SetterPointcut INSTANCE = new SetterPointcut(); + public static final SetterPointcut INSTANCE = new SetterPointcut(); @Override public boolean matches(Method method, Class targetClass) { @@ -115,7 +115,7 @@ private Object readResolve() { @SuppressWarnings("serial") private static class GetterPointcut extends StaticMethodMatcherPointcut implements Serializable { - public static GetterPointcut INSTANCE = new GetterPointcut(); + public static final GetterPointcut INSTANCE = new GetterPointcut(); @Override public boolean matches(Method method, Class targetClass) { diff --git a/spring-aop/src/main/java/org/springframework/aop/support/RootClassFilter.java b/spring-aop/src/main/java/org/springframework/aop/support/RootClassFilter.java index 364ff6282ec1..6b8073d69a2a 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/RootClassFilter.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/RootClassFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,8 @@ import org.springframework.aop.ClassFilter; /** - * Simple ClassFilter implementation that passes classes (and optionally subclasses) + * Simple ClassFilter implementation that passes classes (and optionally subclasses). + * * @author Rod Johnson */ @SuppressWarnings("serial") @@ -37,7 +38,7 @@ public RootClassFilter(Class clazz) { @Override public boolean matches(Class candidate) { - return clazz.isAssignableFrom(candidate); + return this.clazz.isAssignableFrom(candidate); } } diff --git a/spring-aop/src/main/java/org/springframework/aop/support/StaticMethodMatcher.java b/spring-aop/src/main/java/org/springframework/aop/support/StaticMethodMatcher.java index 0627248ee97e..923daaf94d06 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/StaticMethodMatcher.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/StaticMethodMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,8 @@ /** * Convenient abstract superclass for static method matchers, which don't care * about arguments at runtime. + * + * @author Rod Johnson */ public abstract class StaticMethodMatcher implements MethodMatcher { diff --git a/spring-aop/src/main/java/org/springframework/aop/support/StaticMethodMatcherPointcutAdvisor.java b/spring-aop/src/main/java/org/springframework/aop/support/StaticMethodMatcherPointcutAdvisor.java index 9dbe10dbf4fe..3ba109a1ac41 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/StaticMethodMatcherPointcutAdvisor.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/StaticMethodMatcherPointcutAdvisor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,10 +36,10 @@ public abstract class StaticMethodMatcherPointcutAdvisor extends StaticMethodMatcherPointcut implements PointcutAdvisor, Ordered, Serializable { - private int order = Integer.MAX_VALUE; - private Advice advice; + private int order = Integer.MAX_VALUE; + /** * Create a new StaticMethodMatcherPointcutAdvisor, diff --git a/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMatchingPointcut.java b/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMatchingPointcut.java index 59619830d921..ef2790d6f7d3 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMatchingPointcut.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMatchingPointcut.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ import org.springframework.aop.MethodMatcher; import org.springframework.aop.Pointcut; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; /** * Simple Pointcut that looks for a specific Java 5 annotation @@ -46,16 +45,15 @@ public class AnnotationMatchingPointcut implements Pointcut { * @param classAnnotationType the annotation type to look for at the class level */ public AnnotationMatchingPointcut(Class classAnnotationType) { - this.classFilter = new AnnotationClassFilter(classAnnotationType); - this.methodMatcher = MethodMatcher.TRUE; + this(classAnnotationType, false); } /** * Create a new AnnotationMatchingPointcut for the given annotation type. * @param classAnnotationType the annotation type to look for at the class level - * @param checkInherited whether to explicitly check the superclasses and - * interfaces for the annotation type as well (even if the annotation type - * is not marked as inherited itself) + * @param checkInherited whether to also check the superclasses and interfaces + * as well as meta-annotations for the annotation type + * @see AnnotationClassFilter#AnnotationClassFilter(Class, boolean) */ public AnnotationMatchingPointcut(Class classAnnotationType, boolean checkInherited) { this.classFilter = new AnnotationClassFilter(classAnnotationType, checkInherited); @@ -109,21 +107,14 @@ public boolean equals(Object other) { if (!(other instanceof AnnotationMatchingPointcut)) { return false; } - AnnotationMatchingPointcut that = (AnnotationMatchingPointcut) other; - return ObjectUtils.nullSafeEquals(that.classFilter, this.classFilter) && - ObjectUtils.nullSafeEquals(that.methodMatcher, this.methodMatcher); + AnnotationMatchingPointcut otherPointcut = (AnnotationMatchingPointcut) other; + return (this.classFilter.equals(otherPointcut.classFilter) && + this.methodMatcher.equals(otherPointcut.methodMatcher)); } @Override public int hashCode() { - int code = 17; - if (this.classFilter != null) { - code = 37 * code + this.classFilter.hashCode(); - } - if (this.methodMatcher != null) { - code = 37 * code + this.methodMatcher.hashCode(); - } - return code; + return this.classFilter.hashCode() * 37 + this.methodMatcher.hashCode(); } @Override 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 3d0e9ae4f5ac..b070b628eada 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,7 +60,7 @@ public abstract class AbstractBeanFactoryBasedTargetSource implements TargetSour private String targetBeanName; /** Class of the target */ - private Class targetClass; + private volatile Class targetClass; /** * BeanFactory that owns this TargetSource. We need to hold onto this @@ -120,21 +120,28 @@ public BeanFactory getBeanFactory() { @Override - public synchronized Class getTargetClass() { - if (this.targetClass == null && this.beanFactory != null) { - // Determine type of the target bean. - this.targetClass = this.beanFactory.getType(this.targetBeanName); - if (this.targetClass == null) { - if (logger.isTraceEnabled()) { - logger.trace("Getting bean with name '" + this.targetBeanName + "' in order to determine type"); - } - Object beanInstance = this.beanFactory.getBean(this.targetBeanName); - if (beanInstance != null) { - this.targetClass = beanInstance.getClass(); + public Class getTargetClass() { + Class targetClass = this.targetClass; + if (targetClass != null) { + return targetClass; + } + synchronized (this) { + // Full check within synchronization, entering the BeanFactory interaction algorithm only once... + targetClass = this.targetClass; + if (targetClass == null && this.beanFactory != null) { + // Determine type of the target bean. + targetClass = this.beanFactory.getType(this.targetBeanName); + if (targetClass == null) { + if (logger.isTraceEnabled()) { + logger.trace("Getting bean with name '" + this.targetBeanName + "' for type determination"); + } + Object beanInstance = this.beanFactory.getBean(this.targetBeanName); + targetClass = beanInstance.getClass(); } + this.targetClass = targetClass; } + return targetClass; } - return this.targetClass; } @Override diff --git a/spring-aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java index 415d0e57e12a..450f431abeb0 100644 --- a/spring-aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java +++ b/spring-aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -74,8 +74,8 @@ protected Object newPrototypeInstance() throws BeansException { * @param target the bean instance to destroy */ protected void destroyPrototypeInstance(Object target) { - if (this.logger.isDebugEnabled()) { - this.logger.debug("Destroying instance of bean '" + getTargetBeanName() + "'"); + if (logger.isDebugEnabled()) { + logger.debug("Destroying instance of bean '" + getTargetBeanName() + "'"); } if (getBeanFactory() instanceof ConfigurableBeanFactory) { ((ConfigurableBeanFactory) getBeanFactory()).destroyBean(getTargetBeanName(), target); diff --git a/spring-aop/src/main/java/org/springframework/aop/target/dynamic/AbstractRefreshableTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/dynamic/AbstractRefreshableTargetSource.java index c96c21e19312..7bab7e077115 100644 --- a/spring-aop/src/main/java/org/springframework/aop/target/dynamic/AbstractRefreshableTargetSource.java +++ b/spring-aop/src/main/java/org/springframework/aop/target/dynamic/AbstractRefreshableTargetSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,7 +39,7 @@ public abstract class AbstractRefreshableTargetSource implements TargetSource, Refreshable { /** Logger available to subclasses */ - protected Log logger = LogFactory.getLog(getClass()); + protected final Log logger = LogFactory.getLog(getClass()); protected Object targetObject; diff --git a/spring-aop/src/test/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPointTests.java b/spring-aop/src/test/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPointTests.java index f8c39e7b44e1..37f8094b5dc8 100644 --- a/spring-aop/src/test/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPointTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPointTests.java @@ -44,7 +44,7 @@ * @author Ramnivas Laddad * @since 2.0 */ -public final class MethodInvocationProceedingJoinPointTests { +public class MethodInvocationProceedingJoinPointTests { @Test public void testingBindingWithJoinPoint() { @@ -217,7 +217,7 @@ public void before(Method method, Object[] args, Object target) throws Throwable itb.unreliableFileOperation(); } catch (IOException ex) { - // we don't realy care... + // we don't really care... } } diff --git a/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/ArgumentBindingTests.java b/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/ArgumentBindingTests.java index 424505004d1f..08740b5ade62 100644 --- a/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/ArgumentBindingTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/ArgumentBindingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,26 +37,26 @@ * @author Juergen Hoeller * @author Chris Beams */ -public final class ArgumentBindingTests { +public class ArgumentBindingTests { - @Test(expected=IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void testBindingInPointcutUsedByAdvice() { TestBean tb = new TestBean(); AspectJProxyFactory proxyFactory = new AspectJProxyFactory(tb); proxyFactory.addAspect(NamedPointcutWithArgs.class); - ITestBean proxiedTestBean = (ITestBean) proxyFactory.getProxy(); - proxiedTestBean.setName("Supercalifragalisticexpialidocious"); // should throw + ITestBean proxiedTestBean = proxyFactory.getProxy(); + proxiedTestBean.setName("Supercalifragalisticexpialidocious"); } - @Test(expected=IllegalStateException.class) + @Test(expected = IllegalStateException.class) public void testAnnotationArgumentNameBinding() { TransactionalBean tb = new TransactionalBean(); AspectJProxyFactory proxyFactory = new AspectJProxyFactory(tb); proxyFactory.addAspect(PointcutWithAnnotationArgument.class); - ITransactionalBean proxiedTestBean = (ITransactionalBean) proxyFactory.getProxy(); - proxiedTestBean.doInTransaction(); // should throw + ITransactionalBean proxiedTestBean = proxyFactory.getProxy(); + proxiedTestBean.doInTransaction(); } @Test @@ -71,6 +71,7 @@ public void testParameterNameDiscoverWithReferencePointcut() throws Exception { assertEquals("formal", pnames[0]); } + public void methodWithOneParam(String aParam) { } @@ -100,9 +101,6 @@ public void doInTransaction() { } -/** - * @author Juergen Hoeller - */ @Aspect class PointcutWithAnnotationArgument { @@ -115,9 +113,6 @@ public Object around(ProceedingJoinPoint pjp, Transactional transaction) throws } -/** - * @author Adrian Colyer - */ @Aspect class NamedPointcutWithArgs { 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 8d4c43a6e7db..96b2d1843016 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,7 @@ public final class ConcurrencyThrottleInterceptorTests { public void testSerializable() throws Exception { DerivedTestBean tb = new DerivedTestBean(); ProxyFactory proxyFactory = new ProxyFactory(); - proxyFactory.setInterfaces(new Class[] {ITestBean.class}); + proxyFactory.setInterfaces(ITestBean.class); ConcurrencyThrottleInterceptor cti = new ConcurrencyThrottleInterceptor(); proxyFactory.addAdvice(cti); proxyFactory.setTarget(tb); @@ -75,7 +75,7 @@ public void testMultipleThreadsWithLimit10() { private void testMultipleThreads(int concurrencyLimit) { TestBean tb = new TestBean(); ProxyFactory proxyFactory = new ProxyFactory(); - proxyFactory.setInterfaces(new Class[] {ITestBean.class}); + proxyFactory.setInterfaces(ITestBean.class); ConcurrencyThrottleInterceptor cti = new ConcurrencyThrottleInterceptor(); cti.setConcurrencyLimit(concurrencyLimit); proxyFactory.addAdvice(cti); @@ -95,7 +95,7 @@ private void testMultipleThreads(int concurrencyLimit) { ex.printStackTrace(); } threads[i] = new ConcurrencyThread(proxy, - i % 2 == 0 ? (Throwable) new OutOfMemoryError() : (Throwable) new IllegalStateException()); + i % 2 == 0 ? new OutOfMemoryError() : new IllegalStateException()); threads[i].start(); } for (int i = 0; i < NR_OF_THREADS; i++) { diff --git a/spring-aop/src/test/java/org/springframework/aop/interceptor/CustomizableTraceInterceptorTests.java b/spring-aop/src/test/java/org/springframework/aop/interceptor/CustomizableTraceInterceptorTests.java index 6146571fa6f2..7d17d49e17ee 100644 --- a/spring-aop/src/test/java/org/springframework/aop/interceptor/CustomizableTraceInterceptorTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/interceptor/CustomizableTraceInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -83,7 +83,7 @@ public void testSetExceptionMethodWithReturnValuePlaceholder() { public void testSunnyDayPathLogsCorrectly() throws Throwable { MethodInvocation methodInvocation = mock(MethodInvocation.class); - given(methodInvocation.getMethod()).willReturn(String.class.getMethod("toString", new Class[]{})); + given(methodInvocation.getMethod()).willReturn(String.class.getMethod("toString")); given(methodInvocation.getThis()).willReturn(this); Log log = mock(Log.class); @@ -101,7 +101,7 @@ public void testExceptionPathLogsCorrectly() throws Throwable { MethodInvocation methodInvocation = mock(MethodInvocation.class); IllegalArgumentException exception = new IllegalArgumentException(); - given(methodInvocation.getMethod()).willReturn(String.class.getMethod("toString", new Class[]{})); + given(methodInvocation.getMethod()).willReturn(String.class.getMethod("toString")); given(methodInvocation.getThis()).willReturn(this); given(methodInvocation.proceed()).willThrow(exception); diff --git a/spring-aop/src/test/java/org/springframework/aop/interceptor/SimpleTraceInterceptorTests.java b/spring-aop/src/test/java/org/springframework/aop/interceptor/SimpleTraceInterceptorTests.java index 3b811b244315..9c9d0c2ece72 100644 --- a/spring-aop/src/test/java/org/springframework/aop/interceptor/SimpleTraceInterceptorTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/interceptor/SimpleTraceInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ public final class SimpleTraceInterceptorTests { @Test public void testSunnyDayPathLogsCorrectly() throws Throwable { MethodInvocation mi = mock(MethodInvocation.class); - given(mi.getMethod()).willReturn(String.class.getMethod("toString", new Class[]{})); + given(mi.getMethod()).willReturn(String.class.getMethod("toString")); given(mi.getThis()).willReturn(this); Log log = mock(Log.class); @@ -48,7 +48,7 @@ public void testSunnyDayPathLogsCorrectly() throws Throwable { @Test public void testExceptionPathStillLogsCorrectly() throws Throwable { MethodInvocation mi = mock(MethodInvocation.class); - given(mi.getMethod()).willReturn(String.class.getMethod("toString", new Class[]{})); + given(mi.getMethod()).willReturn(String.class.getMethod("toString")); given(mi.getThis()).willReturn(this); IllegalArgumentException exception = new IllegalArgumentException(); given(mi.proceed()).willThrow(exception); diff --git a/spring-aop/src/test/java/org/springframework/aop/scope/ScopedProxyAutowireTests.java b/spring-aop/src/test/java/org/springframework/aop/scope/ScopedProxyAutowireTests.java index 9886a0a145cd..4b5634f2b0aa 100644 --- a/spring-aop/src/test/java/org/springframework/aop/scope/ScopedProxyAutowireTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/scope/ScopedProxyAutowireTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.aop.scope; +import java.util.Arrays; + import org.junit.Test; import org.springframework.beans.factory.support.DefaultListableBeanFactory; @@ -27,19 +29,24 @@ /** * @author Mark Fisher + * @author Juergen Hoeller * @author Chris Beams */ -public final class ScopedProxyAutowireTests { +public class ScopedProxyAutowireTests { - private static final Class CLASS = ScopedProxyAutowireTests.class; + private static final Resource SCOPED_AUTOWIRE_FALSE_CONTEXT = + qualifiedResource(ScopedProxyAutowireTests.class, "scopedAutowireFalse.xml"); + private static final Resource SCOPED_AUTOWIRE_TRUE_CONTEXT = + qualifiedResource(ScopedProxyAutowireTests.class, "scopedAutowireTrue.xml"); - private static final Resource SCOPED_AUTOWIRE_TRUE_CONTEXT = qualifiedResource(CLASS, "scopedAutowireTrue.xml"); - private static final Resource SCOPED_AUTOWIRE_FALSE_CONTEXT = qualifiedResource(CLASS, "scopedAutowireFalse.xml"); @Test public void testScopedProxyInheritsAutowireCandidateFalse() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions(SCOPED_AUTOWIRE_FALSE_CONTEXT); + assertTrue(Arrays.asList(bf.getBeanNamesForType(TestBean.class, false, false)).contains("scoped")); + assertTrue(Arrays.asList(bf.getBeanNamesForType(TestBean.class, true, false)).contains("scoped")); + assertFalse(bf.containsSingleton("scoped")); TestBean autowired = (TestBean) bf.getBean("autowired"); TestBean unscoped = (TestBean) bf.getBean("unscoped"); assertSame(unscoped, autowired.getChild()); @@ -49,6 +56,9 @@ public void testScopedProxyInheritsAutowireCandidateFalse() { public void testScopedProxyReplacesAutowireCandidateTrue() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions(SCOPED_AUTOWIRE_TRUE_CONTEXT); + assertTrue(Arrays.asList(bf.getBeanNamesForType(TestBean.class, true, false)).contains("scoped")); + assertTrue(Arrays.asList(bf.getBeanNamesForType(TestBean.class, false, false)).contains("scoped")); + assertFalse(bf.containsSingleton("scoped")); TestBean autowired = (TestBean) bf.getBean("autowired"); TestBean scoped = (TestBean) bf.getBean("scoped"); assertSame(scoped, autowired.getChild()); diff --git a/spring-aop/src/test/java/org/springframework/tests/aop/interceptor/NopInterceptor.java b/spring-aop/src/test/java/org/springframework/tests/aop/interceptor/NopInterceptor.java index 95dff09cc044..de49c8af7fce 100644 --- a/spring-aop/src/test/java/org/springframework/tests/aop/interceptor/NopInterceptor.java +++ b/spring-aop/src/test/java/org/springframework/tests/aop/interceptor/NopInterceptor.java @@ -1,6 +1,5 @@ - /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,28 +28,22 @@ public class NopInterceptor implements MethodInterceptor { private int count; - /** - * @see org.aopalliance.intercept.MethodInterceptor#invoke(MethodInvocation) - */ + @Override public Object invoke(MethodInvocation invocation) throws Throwable { increment(); return invocation.proceed(); } - public int getCount() { - return this.count; - } - protected void increment() { - ++count; + this.count++; } - @Override - public int hashCode() { - return 0; + public int getCount() { + return this.count; } + @Override public boolean equals(Object other) { if (!(other instanceof NopInterceptor)) { @@ -62,5 +55,9 @@ public boolean equals(Object other) { return this.count == ((NopInterceptor) other).count; } + @Override + public int hashCode() { + return NopInterceptor.class.hashCode(); + } } diff --git a/spring-aop/src/test/java/org/springframework/tests/sample/beans/SerializablePerson.java b/spring-aop/src/test/java/org/springframework/tests/sample/beans/SerializablePerson.java index 805dabd41ad0..bfa856144a52 100644 --- a/spring-aop/src/test/java/org/springframework/tests/sample/beans/SerializablePerson.java +++ b/spring-aop/src/test/java/org/springframework/tests/sample/beans/SerializablePerson.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,31 +28,29 @@ @SuppressWarnings("serial") public class SerializablePerson implements Person, Serializable { - private static final long serialVersionUID = 1L; - - private String name; private int age; + @Override - public int getAge() { - return age; + public String getName() { + return name; } @Override - public void setAge(int age) { - this.age = age; + public void setName(String name) { + this.name = name; } @Override - public String getName() { - return name; + public int getAge() { + return age; } @Override - public void setName(String name) { - this.name = name; + public void setAge(int age) { + this.age = age; } @Override @@ -63,10 +61,6 @@ public Object echo(Object o) throws Throwable { return o; } - @Override - public int hashCode() { - return 0; - } @Override public boolean equals(Object other) { @@ -77,4 +71,9 @@ public boolean equals(Object other) { return p.age == age && ObjectUtils.nullSafeEquals(name, p.name); } + @Override + public int hashCode() { + return SerializablePerson.class.hashCode(); + } + } diff --git a/spring-aop/src/test/resources/org/springframework/aop/config/TopLevelAopTagTests-context.xml b/spring-aop/src/test/resources/org/springframework/aop/config/TopLevelAopTagTests-context.xml index 6c9e44b560f9..f1fff08fa4a7 100644 --- a/spring-aop/src/test/resources/org/springframework/aop/config/TopLevelAopTagTests-context.xml +++ b/spring-aop/src/test/resources/org/springframework/aop/config/TopLevelAopTagTests-context.xml @@ -1,6 +1,6 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> @@ -9,4 +9,4 @@ - \ No newline at end of file + diff --git a/spring-aop/src/test/resources/org/springframework/aop/scope/ScopedProxyAutowireTests-scopedAutowireFalse.xml b/spring-aop/src/test/resources/org/springframework/aop/scope/ScopedProxyAutowireTests-scopedAutowireFalse.xml index feecab3e9cf7..5cb859c8c09f 100644 --- a/spring-aop/src/test/resources/org/springframework/aop/scope/ScopedProxyAutowireTests-scopedAutowireFalse.xml +++ b/spring-aop/src/test/resources/org/springframework/aop/scope/ScopedProxyAutowireTests-scopedAutowireFalse.xml @@ -1,16 +1,16 @@ + xmlns:aop="http://www.springframework.org/schema/aop" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> - - - + + + - + - + diff --git a/spring-aop/src/test/resources/org/springframework/aop/scope/ScopedProxyAutowireTests-scopedAutowireTrue.xml b/spring-aop/src/test/resources/org/springframework/aop/scope/ScopedProxyAutowireTests-scopedAutowireTrue.xml index 445b50d064b7..22c62644dd86 100644 --- a/spring-aop/src/test/resources/org/springframework/aop/scope/ScopedProxyAutowireTests-scopedAutowireTrue.xml +++ b/spring-aop/src/test/resources/org/springframework/aop/scope/ScopedProxyAutowireTests-scopedAutowireTrue.xml @@ -1,16 +1,16 @@ + xmlns:aop="http://www.springframework.org/schema/aop" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> - - - + + + - + - + diff --git a/spring-aspects/aspects.gradle b/spring-aspects/aspects.gradle index 2c68dff4da93..78491cc0bda3 100644 --- a/spring-aspects/aspects.gradle +++ b/spring-aspects/aspects.gradle @@ -1,5 +1,4 @@ -// redefine the compileJava and compileTestJava tasks in order to -// compile sources with ajc instead of javac +// Redefine the compileJava and compileTestJava tasks in order to compile sources with ajc instead of javac configurations { rt @@ -8,22 +7,14 @@ configurations { ajInpath } -// exclude spring-aspects as a module within IDEA until IDEA-64446 is resolved -tasks.getByName("idea").onlyIf { false } -tasks.getByName("ideaModule").onlyIf { false } - compileJava { actions = [] dependsOn configurations.ajc.getTaskDependencyFromProjectDependency(true, "compileJava") def outputDir = project.sourceSets.main.output.classesDir - inputs.files(project.sourceSets.main.allSource + project.sourceSets.main.compileClasspath) outputs.dir outputDir - ext.sourceCompatibility = project(":spring-core").compileJava.sourceCompatibility - ext.targetCompatibility = project(":spring-core").compileJava.targetCompatibility - doLast{ // Assemble runtime classpath from folders and JARs that actually exist def runtimeClasspath = project.files(sourceSets.main.runtimeClasspath.files.findAll({ it.exists() })) @@ -53,13 +44,9 @@ compileTestJava { dependsOn jar def outputDir = project.sourceSets.test.output.classesDir - inputs.files(project.sourceSets.test.allSource + project.sourceSets.test.compileClasspath) outputs.dir outputDir - ext.sourceCompatibility = project(":spring-core").compileTestJava.sourceCompatibility - ext.targetCompatibility = project(":spring-core").compileTestJava.targetCompatibility - doLast{ // Assemble runtime classpath from folders and JARs that actually exist def runtimeClasspath = project.files(sourceSets.test.runtimeClasspath.files.findAll({ it.exists() })) diff --git a/spring-aspects/src/main/java/org/springframework/cache/aspectj/AspectJCachingConfiguration.java b/spring-aspects/src/main/java/org/springframework/cache/aspectj/AspectJCachingConfiguration.java index 9932c79d1220..32e5b77a1557 100644 --- a/spring-aspects/src/main/java/org/springframework/cache/aspectj/AspectJCachingConfiguration.java +++ b/spring-aspects/src/main/java/org/springframework/cache/aspectj/AspectJCachingConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ * @see org.springframework.cache.annotation.CachingConfigurationSelector */ @Configuration +@Role(BeanDefinition.ROLE_INFRASTRUCTURE) public class AspectJCachingConfiguration extends AbstractCachingConfiguration { @Bean(name = CacheManagementConfigUtils.CACHE_ASPECT_BEAN_NAME) diff --git a/spring-aspects/src/main/java/org/springframework/cache/aspectj/AspectJJCacheConfiguration.java b/spring-aspects/src/main/java/org/springframework/cache/aspectj/AspectJJCacheConfiguration.java index 63076f476b6b..73bf36f068ce 100644 --- a/spring-aspects/src/main/java/org/springframework/cache/aspectj/AspectJJCacheConfiguration.java +++ b/spring-aspects/src/main/java/org/springframework/cache/aspectj/AspectJJCacheConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ * @see org.springframework.cache.annotation.CachingConfigurationSelector */ @Configuration +@Role(BeanDefinition.ROLE_INFRASTRUCTURE) public class AspectJJCacheConfiguration extends AbstractJCacheConfiguration { @Bean(name = CacheManagementConfigUtils.JCACHE_ASPECT_BEAN_NAME) diff --git a/spring-aspects/src/main/java/org/springframework/transaction/aspectj/JtaAnnotationTransactionAspect.aj b/spring-aspects/src/main/java/org/springframework/transaction/aspectj/JtaAnnotationTransactionAspect.aj index 6b02e1de3a5d..22a3bb134b55 100644 --- a/spring-aspects/src/main/java/org/springframework/transaction/aspectj/JtaAnnotationTransactionAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/transaction/aspectj/JtaAnnotationTransactionAspect.aj @@ -45,7 +45,7 @@ import org.springframework.transaction.annotation.AnnotationTransactionAttribute * @see javax.transaction.Transactional * @see AnnotationTransactionAspect */ -@RequiredTypes({"javax.transaction.Transactional"}) +@RequiredTypes("javax.transaction.Transactional") public aspect JtaAnnotationTransactionAspect extends AbstractTransactionAspect { public JtaAnnotationTransactionAspect() { diff --git a/spring-aspects/src/test/java/org/springframework/transaction/aspectj/TransactionAspectTests.java b/spring-aspects/src/test/java/org/springframework/transaction/aspectj/TransactionAspectTests.java index 5971b0ae006a..c12bf1204987 100644 --- a/spring-aspects/src/test/java/org/springframework/transaction/aspectj/TransactionAspectTests.java +++ b/spring-aspects/src/test/java/org/springframework/transaction/aspectj/TransactionAspectTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,55 +18,45 @@ import java.lang.reflect.Method; +import org.junit.Before; +import org.junit.Test; + import org.springframework.tests.transaction.CallCountingTransactionManager; import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource; -import org.springframework.transaction.interceptor.TransactionAspectSupport; import org.springframework.transaction.interceptor.TransactionAttribute; +import static org.junit.Assert.*; + /** * @author Rod Johnson * @author Ramnivas Laddad * @author Juergen Hoeller * @author Sam Brannen */ -@SuppressWarnings("deprecation") -public class TransactionAspectTests extends org.springframework.test.AbstractDependencyInjectionSpringContextTests { +public class TransactionAspectTests { - private CallCountingTransactionManager txManager; + private final CallCountingTransactionManager txManager = new CallCountingTransactionManager(); - private TransactionalAnnotationOnlyOnClassWithNoInterface annotationOnlyOnClassWithNoInterface; + private final TransactionalAnnotationOnlyOnClassWithNoInterface annotationOnlyOnClassWithNoInterface = + new TransactionalAnnotationOnlyOnClassWithNoInterface(); - private ClassWithProtectedAnnotatedMember beanWithAnnotatedProtectedMethod; + private final ClassWithProtectedAnnotatedMember beanWithAnnotatedProtectedMethod = + new ClassWithProtectedAnnotatedMember(); - private ClassWithPrivateAnnotatedMember beanWithAnnotatedPrivateMethod; + private final ClassWithPrivateAnnotatedMember beanWithAnnotatedPrivateMethod = + new ClassWithPrivateAnnotatedMember(); - private MethodAnnotationOnClassWithNoInterface methodAnnotationOnly = new MethodAnnotationOnClassWithNoInterface(); + private final MethodAnnotationOnClassWithNoInterface methodAnnotationOnly = + new MethodAnnotationOnClassWithNoInterface(); - public void setAnnotationOnlyOnClassWithNoInterface( - TransactionalAnnotationOnlyOnClassWithNoInterface annotationOnlyOnClassWithNoInterface) { - this.annotationOnlyOnClassWithNoInterface = annotationOnlyOnClassWithNoInterface; - } - - public void setClassWithAnnotatedProtectedMethod(ClassWithProtectedAnnotatedMember aBean) { - this.beanWithAnnotatedProtectedMethod = aBean; - } - - public void setClassWithAnnotatedPrivateMethod(ClassWithPrivateAnnotatedMember aBean) { - this.beanWithAnnotatedPrivateMethod = aBean; - } - - public void setTransactionAspect(TransactionAspectSupport transactionAspect) { - this.txManager = (CallCountingTransactionManager) transactionAspect.getTransactionManager(); - } - - - @Override - protected String[] getConfigPaths() { - return new String[] { "TransactionAspectTests-context.xml" }; + @Before + public void initContext() { + AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager); } + @Test public void testCommitOnAnnotatedClass() throws Throwable { txManager.clear(); assertEquals(0, txManager.begun); @@ -74,6 +64,7 @@ public void testCommitOnAnnotatedClass() throws Throwable { assertEquals(1, txManager.commits); } + @Test public void testCommitOnAnnotatedProtectedMethod() throws Throwable { txManager.clear(); assertEquals(0, txManager.begun); @@ -81,6 +72,7 @@ public void testCommitOnAnnotatedProtectedMethod() throws Throwable { assertEquals(1, txManager.commits); } + @Test public void testCommitOnAnnotatedPrivateMethod() throws Throwable { txManager.clear(); assertEquals(0, txManager.begun); @@ -88,6 +80,7 @@ public void testCommitOnAnnotatedPrivateMethod() throws Throwable { assertEquals(1, txManager.commits); } + @Test public void testNoCommitOnNonAnnotatedNonPublicMethodInTransactionalType() throws Throwable { txManager.clear(); assertEquals(0,txManager.begun); @@ -95,6 +88,7 @@ public void testNoCommitOnNonAnnotatedNonPublicMethodInTransactionalType() throw assertEquals(0,txManager.begun); } + @Test public void testCommitOnAnnotatedMethod() throws Throwable { txManager.clear(); assertEquals(0, txManager.begun); @@ -102,6 +96,7 @@ public void testCommitOnAnnotatedMethod() throws Throwable { assertEquals(1, txManager.commits); } + @Test public void testNotTransactional() throws Throwable { txManager.clear(); assertEquals(0, txManager.begun); @@ -109,6 +104,7 @@ public void testNotTransactional() throws Throwable { assertEquals(0, txManager.begun); } + @Test public void testDefaultCommitOnAnnotatedClass() throws Throwable { final Exception ex = new Exception(); try { @@ -125,6 +121,7 @@ public Object performTransactionalOperation() throws Throwable { } } + @Test public void testDefaultRollbackOnAnnotatedClass() throws Throwable { final RuntimeException ex = new RuntimeException(); try { @@ -141,10 +138,11 @@ public Object performTransactionalOperation() throws Throwable { } } + @Test public void testDefaultCommitOnSubclassOfAnnotatedClass() throws Throwable { final Exception ex = new Exception(); try { - testRollback(new TransactionOperationCallback() { + testRollback(new TransactionOperationCallback() { @Override public Object performTransactionalOperation() throws Throwable { return new SubclassOfClassWithTransactionalAnnotation().echo(ex); @@ -157,6 +155,7 @@ public Object performTransactionalOperation() throws Throwable { } } + @Test public void testDefaultCommitOnSubclassOfClassWithTransactionalMethodAnnotated() throws Throwable { final Exception ex = new Exception(); try { @@ -173,6 +172,7 @@ public Object performTransactionalOperation() throws Throwable { } } + @Test public void testDefaultCommitOnImplementationOfAnnotatedInterface() throws Throwable { final Exception ex = new Exception(); testNotTransactional(new TransactionOperationCallback() { @@ -185,16 +185,19 @@ public Object performTransactionalOperation() throws Throwable { /** * Note: resolution does not occur. Thus we can't make a class transactional if - * it implements a transactionally annotated interface. This behaviour could only + * it implements a transactionally annotated interface. This behavior could only * be changed in AbstractFallbackTransactionAttributeSource in Spring proper. + * See SPR-14322. */ + @Test public void testDoesNotResolveTxAnnotationOnMethodFromClassImplementingAnnotatedInterface() throws Exception { AnnotationTransactionAttributeSource atas = new AnnotationTransactionAttributeSource(); - Method m = ImplementsAnnotatedInterface.class.getMethod("echo", Throwable.class); - TransactionAttribute ta = atas.getTransactionAttribute(m, ImplementsAnnotatedInterface.class); + Method method = ImplementsAnnotatedInterface.class.getMethod("echo", Throwable.class); + TransactionAttribute ta = atas.getTransactionAttribute(method, ImplementsAnnotatedInterface.class); assertNull(ta); } + @Test public void testDefaultRollbackOnImplementationOfAnnotatedInterface() throws Throwable { final Exception rollbackProvokingException = new RuntimeException(); testNotTransactional(new TransactionOperationCallback() { @@ -237,15 +240,19 @@ protected void testNotTransactional(TransactionOperationCallback toc, Throwable private interface TransactionOperationCallback { + Object performTransactionalOperation() throws Throwable; } + public static class SubclassOfClassWithTransactionalAnnotation extends TransactionalAnnotationOnlyOnClassWithNoInterface { } + public static class SubclassOfClassWithTransactionalMethodAnnotation extends MethodAnnotationOnClassWithNoInterface { } + public static class ImplementsAnnotatedInterface implements ITransactional { @Override @@ -257,6 +264,7 @@ public Object echo(Throwable t) throws Throwable { } } + public static class NotTransactional { public void noop() { diff --git a/spring-beans-groovy/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionReader.java b/spring-beans-groovy/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionReader.java index 43d682333191..dbc949dd721a 100644 --- a/spring-beans-groovy/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionReader.java +++ b/spring-beans-groovy/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -237,6 +237,7 @@ public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefin } Closure beans = new Closure(this) { + @Override public Object call(Object[] args) { invokeBeanDefiningClosure((Closure) args[0]); return null; @@ -303,7 +304,7 @@ public AbstractBeanDefinition bean(Class type, Object...args) { Collection constructorArgs = null; if (!ObjectUtils.isEmpty(args)) { int index = args.length; - Object lastArg = args[index-1]; + Object lastArg = args[index - 1]; if (lastArg instanceof Closure) { callable = (Closure) lastArg; index--; @@ -380,10 +381,8 @@ else if ("ref".equals(name)) { refName = args[0].toString(); } boolean parentRef = false; - if (args.length > 1) { - if (args[1] instanceof Boolean) { - parentRef = (Boolean) args[1]; - } + if (args.length > 1 && args[1] instanceof Boolean) { + parentRef = (Boolean) args[1]; } return new RuntimeBeanReference(refName, parentRef); } @@ -410,12 +409,7 @@ else if (args.length > 1 && args[args.length -1] instanceof Closure) { } private boolean addDeferredProperty(String property, Object newValue) { - if (newValue instanceof List) { - this.deferredProperties.put(this.currentBeanDefinition.getBeanName() + '.' + property, - new DeferredProperty(this.currentBeanDefinition, property, newValue)); - return true; - } - else if (newValue instanceof Map) { + if (newValue instanceof List || newValue instanceof Map) { this.deferredProperties.put(this.currentBeanDefinition.getBeanName() + '.' + property, new DeferredProperty(this.currentBeanDefinition, property, newValue)); return true; @@ -456,14 +450,14 @@ protected GroovyBeanDefinitionReader invokeBeanDefiningClosure(Closure callable) * @return the bean definition wrapper */ private GroovyBeanDefinitionWrapper invokeBeanDefiningMethod(String beanName, Object[] args) { - boolean hasClosureArgument = args[args.length - 1] instanceof Closure; + boolean hasClosureArgument = (args[args.length - 1] instanceof Closure); if (args[0] instanceof Class) { - Class beanClass = (args[0] instanceof Class ? (Class) args[0] : args[0].getClass()); + Class beanClass = (Class) args[0]; if (args.length >= 1) { if (hasClosureArgument) { - if (args.length-1 != 1) { + if (args.length - 1 != 1) { this.currentBeanDefinition = new GroovyBeanDefinitionWrapper( - beanName, beanClass, resolveConstructorArguments(args,1,args.length-1)); + beanName, beanClass, resolveConstructorArguments(args, 1, args.length - 1)); } else { this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName, beanClass); @@ -471,7 +465,7 @@ private GroovyBeanDefinitionWrapper invokeBeanDefiningMethod(String beanName, Ob } else { this.currentBeanDefinition = new GroovyBeanDefinitionWrapper( - beanName, beanClass, resolveConstructorArguments(args,1,args.length)); + beanName, beanClass, resolveConstructorArguments(args, 1, args.length)); } } @@ -483,7 +477,7 @@ else if (args[0] instanceof RuntimeBeanReference) { else if (args[0] instanceof Map) { // named constructor arguments if (args.length > 1 && args[1] instanceof Class) { - List constructorArgs = resolveConstructorArguments(args, 2, hasClosureArgument ? args.length-1 : args.length); + List constructorArgs = 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()) { @@ -519,18 +513,18 @@ else if (args[0] instanceof Closure) { this.currentBeanDefinition.getBeanDefinition().setAbstract(true); } else { - List constructorArgs = resolveConstructorArguments(args, 0, hasClosureArgument ? args.length-1 : args.length); + List constructorArgs = resolveConstructorArguments(args, 0, hasClosureArgument ? args.length - 1 : args.length); currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName, null, constructorArgs); } if (hasClosureArgument) { - Closure callable = (Closure)args[args.length-1]; + Closure callable = (Closure) args[args.length - 1]; callable.setDelegate(this); callable.setResolveStrategy(Closure.DELEGATE_FIRST); - callable.call(new Object[]{currentBeanDefinition}); + callable.call(this.currentBeanDefinition); } - GroovyBeanDefinitionWrapper beanDefinition = currentBeanDefinition; + GroovyBeanDefinitionWrapper beanDefinition = this.currentBeanDefinition; this.currentBeanDefinition = null; beanDefinition.getBeanDefinition().setAttribute(GroovyBeanDefinitionWrapper.class.getName(), beanDefinition); getRegistry().registerBeanDefinition(beanName, beanDefinition.getBeanDefinition()); @@ -818,14 +812,17 @@ public boolean addAll(Collection values) { return retVal; } + @Override public Object invokeMethod(String name, Object args) { return InvokerHelper.invokeMethod(this.propertyValue, name, args); } + @Override public Object getProperty(String name) { return InvokerHelper.getProperty(this.propertyValue, name); } + @Override public void setProperty(String name, Object value) { InvokerHelper.setProperty(this.propertyValue, name, value); } diff --git a/spring-beans-groovy/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionWrapper.java b/spring-beans-groovy/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionWrapper.java index 4a70a42c6d21..1a93c9dc55aa 100644 --- a/spring-beans-groovy/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionWrapper.java +++ b/spring-beans-groovy/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -162,6 +162,7 @@ public GroovyBeanDefinitionWrapper addProperty(String propertyName, Object prope } + @Override public Object getProperty(String property) { if (this.definitionWrapper.isReadableProperty(property)) { return this.definitionWrapper.getPropertyValue(property); @@ -172,6 +173,7 @@ else if (dynamicProperties.contains(property)) { return super.getProperty(property); } + @Override public void setProperty(String property, Object newValue) { if (PARENT.equals(property)) { setParent(newValue); 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 f3e3bd14795a..6496e3d6f3ec 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-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,10 +55,10 @@ * as String arrays are converted in such a format if the array itself is not * assignable. * - * @author Rod Johnson * @author Juergen Hoeller - * @author Rob Harrop * @author Stephane Nicoll + * @author Rod Johnson + * @author Rob Harrop * @since 4.2 * @see #registerCustomEditor * @see #setPropertyValues @@ -96,9 +96,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA Object rootObject; - /** - * Map with cached nested Accessors: nested path -> Accessor instance. - */ + /** Map with cached nested Accessors: nested path -> Accessor instance */ private Map nestedPropertyAccessors; @@ -284,198 +282,212 @@ public void setPropertyValue(PropertyValue pv) throws BeansException { } } - @SuppressWarnings("unchecked") protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException { - String propertyName = tokens.canonicalName; - String actualName = tokens.actualName; - if (tokens.keys != null) { - // Apply indexes and map keys: fetch value for all keys but the last one. - PropertyTokenHolder getterTokens = new PropertyTokenHolder(); - getterTokens.canonicalName = tokens.canonicalName; - getterTokens.actualName = tokens.actualName; - getterTokens.keys = new String[tokens.keys.length - 1]; - System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1); - Object propValue; + processKeyedProperty(tokens, pv); + } + else { + processLocalProperty(tokens, pv); + } + } + + @SuppressWarnings("unchecked") + private void processKeyedProperty(PropertyTokenHolder tokens, PropertyValue pv) { + Object propValue = getPropertyHoldingValue(tokens); + String lastKey = tokens.keys[tokens.keys.length - 1]; + + if (propValue.getClass().isArray()) { + PropertyHandler ph = getLocalPropertyHandler(tokens.actualName); + Class requiredType = propValue.getClass().getComponentType(); + int arrayIndex = Integer.parseInt(lastKey); + Object oldValue = null; try { - propValue = getPropertyValue(getterTokens); - } - catch (NotReadablePropertyException ex) { - throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName, - "Cannot access indexed value in property referenced " + - "in indexed property path '" + propertyName + "'", ex); - } - // Set value for last key. - String key = tokens.keys[tokens.keys.length - 1]; - if (propValue == null) { - // null map value case - if (isAutoGrowNestedPaths()) { - // TODO: cleanup, this is pretty hacky - int lastKeyIndex = tokens.canonicalName.lastIndexOf('['); - getterTokens.canonicalName = tokens.canonicalName.substring(0, lastKeyIndex); - propValue = setDefaultValue(getterTokens); + if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) { + oldValue = Array.get(propValue, arrayIndex); } - else { - throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName, - "Cannot access indexed value in property referenced " + - "in indexed property path '" + propertyName + "': returned null"); + Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(), + requiredType, ph.nested(tokens.keys.length)); + int length = Array.getLength(propValue); + if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) { + 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); } + Array.set(propValue, arrayIndex, convertedValue); } - if (propValue.getClass().isArray()) { - PropertyHandler ph = getLocalPropertyHandler(actualName); - Class requiredType = propValue.getClass().getComponentType(); - int arrayIndex = Integer.parseInt(key); - Object oldValue = null; - try { - if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) { - oldValue = Array.get(propValue, arrayIndex); + catch (IndexOutOfBoundsException ex) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName, + "Invalid array index in property path '" + tokens.canonicalName + "'", ex); + } + } + + else if (propValue instanceof List) { + PropertyHandler ph = getPropertyHandler(tokens.actualName); + Class requiredType = ph.getCollectionType(tokens.keys.length); + List list = (List) propValue; + int index = Integer.parseInt(lastKey); + Object oldValue = null; + if (isExtractOldValueForEditor() && index < list.size()) { + oldValue = list.get(index); + } + Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(), + requiredType, ph.nested(tokens.keys.length)); + int size = list.size(); + if (index >= size && index < this.autoGrowCollectionLimit) { + for (int i = size; i < index; i++) { + try { + list.add(null); } - Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), - requiredType, ph.nested(tokens.keys.length)); - int length = Array.getLength(propValue); - if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) { - Class componentType = propValue.getClass().getComponentType(); - Object newArray = Array.newInstance(componentType, arrayIndex + 1); - System.arraycopy(propValue, 0, newArray, 0, length); - setPropertyValue(actualName, newArray); - propValue = getPropertyValue(actualName); + catch (NullPointerException ex) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName, + "Cannot set element with index " + index + " in List of size " + + size + ", accessed using property path '" + tokens.canonicalName + + "': List does not support filling up gaps with null elements"); } - Array.set(propValue, arrayIndex, convertedValue); - } - catch (IndexOutOfBoundsException ex) { - throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, - "Invalid array index in property path '" + propertyName + "'", ex); } + list.add(convertedValue); } - else if (propValue instanceof List) { - PropertyHandler ph = getPropertyHandler(actualName); - Class requiredType = ph.getCollectionType(tokens.keys.length); - List list = (List) propValue; - int index = Integer.parseInt(key); - Object oldValue = null; - if (isExtractOldValueForEditor() && index < list.size()) { - oldValue = list.get(index); - } - Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), - requiredType, ph.nested(tokens.keys.length)); - int size = list.size(); - if (index >= size && index < this.autoGrowCollectionLimit) { - for (int i = size; i < index; i++) { - try { - list.add(null); - } - catch (NullPointerException ex) { - throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, - "Cannot set element with index " + index + " in List of size " + - size + ", accessed using property path '" + propertyName + - "': List does not support filling up gaps with null elements"); - } - } - list.add(convertedValue); + else { + try { + list.set(index, convertedValue); } - else { - try { - list.set(index, convertedValue); - } - catch (IndexOutOfBoundsException ex) { - throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, - "Invalid list index in property path '" + propertyName + "'", ex); - } + catch (IndexOutOfBoundsException ex) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName, + "Invalid list index in property path '" + tokens.canonicalName + "'", ex); } } - else if (propValue instanceof Map) { - PropertyHandler ph = getLocalPropertyHandler(actualName); - Class mapKeyType = ph.getMapKeyType(tokens.keys.length); - Class mapValueType = ph.getMapValueType(tokens.keys.length); - Map map = (Map) propValue; - // IMPORTANT: Do not pass full property name in here - property editors - // must not kick in for map keys but rather only for map values. - TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType); - Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor); - Object oldValue = null; - if (isExtractOldValueForEditor()) { - oldValue = map.get(convertedMapKey); + } + + else if (propValue instanceof Map) { + PropertyHandler ph = getLocalPropertyHandler(tokens.actualName); + Class mapKeyType = ph.getMapKeyType(tokens.keys.length); + Class mapValueType = ph.getMapValueType(tokens.keys.length); + Map map = (Map) propValue; + // IMPORTANT: Do not pass full property name in here - property editors + // must not kick in for map keys but rather only for map values. + TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType); + Object convertedMapKey = convertIfNecessary(null, null, lastKey, mapKeyType, typeDescriptor); + Object oldValue = null; + if (isExtractOldValueForEditor()) { + oldValue = map.get(convertedMapKey); + } + // Pass full property name and old value in here, since we want full + // conversion ability for map values. + Object convertedMapValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(), + mapValueType, ph.nested(tokens.keys.length)); + map.put(convertedMapKey, convertedMapValue); + } + + else { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName, + "Property referenced in indexed property path '" + tokens.canonicalName + + "' is neither an array nor a List nor a Map; returned value was [" + propValue + "]"); + } + } + + private Object getPropertyHoldingValue(PropertyTokenHolder tokens) { + // Apply indexes and map keys: fetch value for all keys but the last one. + PropertyTokenHolder getterTokens = new PropertyTokenHolder(); + getterTokens.canonicalName = tokens.canonicalName; + getterTokens.actualName = tokens.actualName; + getterTokens.keys = new String[tokens.keys.length - 1]; + System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1); + + Object propValue; + try { + propValue = getPropertyValue(getterTokens); + } + catch (NotReadablePropertyException ex) { + throw new NotWritablePropertyException(getRootClass(), this.nestedPath + tokens.canonicalName, + "Cannot access indexed value in property referenced " + + "in indexed property path '" + tokens.canonicalName + "'", ex); + } + + if (propValue == null) { + // null map value case + if (isAutoGrowNestedPaths()) { + int lastKeyIndex = tokens.canonicalName.lastIndexOf('['); + getterTokens.canonicalName = tokens.canonicalName.substring(0, lastKeyIndex); + propValue = setDefaultValue(getterTokens); + } + else { + throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + tokens.canonicalName, + "Cannot access indexed value in property referenced " + + "in indexed property path '" + tokens.canonicalName + "': returned null"); + } + } + return propValue; + } + + private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) { + PropertyHandler ph = getLocalPropertyHandler(tokens.actualName); + if (ph == null || !ph.isWritable()) { + if (pv.isOptional()) { + if (logger.isDebugEnabled()) { + logger.debug("Ignoring optional value for property '" + tokens.actualName + + "' - property not found on bean class [" + getRootClass().getName() + "]"); } - // Pass full property name and old value in here, since we want full - // conversion ability for map values. - Object convertedMapValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), - mapValueType, ph.nested(tokens.keys.length)); - map.put(convertedMapKey, convertedMapValue); + return; } else { - throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, - "Property referenced in indexed property path '" + propertyName + - "' is neither an array nor a List nor a Map; returned value was [" + propValue + "]"); + throw createNotWritablePropertyException(tokens.canonicalName); } } - else { - PropertyHandler ph = getLocalPropertyHandler(actualName); - if (ph == null || !ph.isWritable()) { - if (pv.isOptional()) { - if (logger.isDebugEnabled()) { - logger.debug("Ignoring optional value for property '" + actualName + - "' - property not found on bean class [" + getRootClass().getName() + "]"); - } - return; + Object oldValue = null; + try { + Object originalValue = pv.getValue(); + Object valueToApply = originalValue; + if (!Boolean.FALSE.equals(pv.conversionNecessary)) { + if (pv.isConverted()) { + valueToApply = pv.getConvertedValue(); } else { - throw createNotWritablePropertyException(propertyName); - } - } - Object oldValue = null; - try { - Object originalValue = pv.getValue(); - Object valueToApply = originalValue; - if (!Boolean.FALSE.equals(pv.conversionNecessary)) { - if (pv.isConverted()) { - valueToApply = pv.getConvertedValue(); - } - else { - if (isExtractOldValueForEditor() && ph.isReadable()) { - try { - oldValue = ph.getValue(); + if (isExtractOldValueForEditor() && ph.isReadable()) { + try { + oldValue = ph.getValue(); + } + catch (Exception ex) { + if (ex instanceof PrivilegedActionException) { + ex = ((PrivilegedActionException) ex).getException(); } - catch (Exception ex) { - if (ex instanceof PrivilegedActionException) { - ex = ((PrivilegedActionException) ex).getException(); - } - if (logger.isDebugEnabled()) { - logger.debug("Could not read previous value of property '" + - this.nestedPath + propertyName + "'", ex); - } + if (logger.isDebugEnabled()) { + logger.debug("Could not read previous value of property '" + + this.nestedPath + tokens.canonicalName + "'", ex); } } - valueToApply = convertForProperty( - propertyName, oldValue, originalValue, ph.toTypeDescriptor()); } - pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue); + valueToApply = convertForProperty( + tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor()); } - ph.setValue(this.wrappedObject, valueToApply); + pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue); } - catch (TypeMismatchException ex) { - throw ex; + ph.setValue(this.wrappedObject, valueToApply); + } + catch (TypeMismatchException ex) { + throw ex; + } + catch (InvocationTargetException ex) { + PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent( + this.rootObject, this.nestedPath + tokens.canonicalName, oldValue, pv.getValue()); + if (ex.getTargetException() instanceof ClassCastException) { + throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException()); } - catch (InvocationTargetException ex) { - PropertyChangeEvent propertyChangeEvent = - new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); - if (ex.getTargetException() instanceof ClassCastException) { - throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException()); - } - else { - Throwable cause = ex.getTargetException(); - if (cause instanceof UndeclaredThrowableException) { - // May happen e.g. with Groovy-generated methods - cause = cause.getCause(); - } - throw new MethodInvocationException(propertyChangeEvent, cause); + else { + Throwable cause = ex.getTargetException(); + if (cause instanceof UndeclaredThrowableException) { + // May happen e.g. with Groovy-generated methods + cause = cause.getCause(); } + throw new MethodInvocationException(propertyChangeEvent, cause); } - catch (Exception ex) { - PropertyChangeEvent pce = - new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); - throw new MethodInvocationException(pce, ex); - } + } + catch (Exception ex) { + PropertyChangeEvent pce = new PropertyChangeEvent( + this.rootObject, this.nestedPath + tokens.canonicalName, oldValue, pv.getValue()); + throw new MethodInvocationException(pce, ex); } } @@ -590,7 +602,7 @@ private Object convertIfNecessary(String propertyName, Object oldValue, Object n new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, newValue); throw new ConversionNotSupportedException(pce, requiredType, ex); } - catch (Throwable ex) { + catch (IllegalArgumentException ex) { PropertyChangeEvent pce = new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, newValue); throw new TypeMismatchException(pce, requiredType, ex); @@ -973,9 +985,6 @@ public String toString() { } - /** - * Handle a given property. - */ protected abstract static class PropertyHandler { private final Class propertyType; 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 50c04c3b2f6b..0cdf050a2aae 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-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -311,23 +311,23 @@ else if (!method.isBridge() && targetMethod.getParameterTypes().length == numPar public static Method resolveSignature(String signature, Class clazz) { Assert.hasText(signature, "'signature' must not be empty"); Assert.notNull(clazz, "Class must not be null"); - int firstParen = signature.indexOf("("); - int lastParen = signature.indexOf(")"); - if (firstParen > -1 && lastParen == -1) { + int startParen = signature.indexOf('('); + int endParen = signature.indexOf(')'); + if (startParen > -1 && endParen == -1) { throw new IllegalArgumentException("Invalid method signature '" + signature + "': expected closing ')' for args list"); } - else if (lastParen > -1 && firstParen == -1) { + else if (startParen == -1 && endParen > -1) { throw new IllegalArgumentException("Invalid method signature '" + signature + "': expected opening '(' for args list"); } - else if (firstParen == -1 && lastParen == -1) { + else if (startParen == -1 && endParen == -1) { return findMethodWithMinimalParameters(clazz, signature); } else { - String methodName = signature.substring(0, firstParen); + String methodName = signature.substring(0, startParen); String[] parameterTypeNames = - StringUtils.commaDelimitedListToStringArray(signature.substring(firstParen + 1, lastParen)); + StringUtils.commaDelimitedListToStringArray(signature.substring(startParen + 1, endParen)); Class[] parameterTypes = new Class[parameterTypeNames.length]; for (int i = 0; i < parameterTypeNames.length; i++) { String parameterTypeName = parameterTypeNames[i].trim(); @@ -506,13 +506,14 @@ public static boolean isSimpleProperty(Class clazz) { /** * Check if the given type represents a "simple" value type: - * a primitive, a String or other CharSequence, a Number, a Date, + * a primitive, an enum, a String or other CharSequence, a Number, a Date, * a URI, a URL, a Locale or a Class. * @param clazz the type to check * @return whether the given type represents a "simple" value type */ public static boolean isSimpleValueType(Class clazz) { - return (ClassUtils.isPrimitiveOrWrapper(clazz) || clazz.isEnum() || + return (ClassUtils.isPrimitiveOrWrapper(clazz) || + Enum.class.isAssignableFrom(clazz) || CharSequence.class.isAssignableFrom(clazz) || Number.class.isAssignableFrom(clazz) || Date.class.isAssignableFrom(clazz) || diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java b/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java index 4d10dc0c8185..a1686d0e80a7 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -224,10 +224,7 @@ private Property property(PropertyDescriptor pd) { @Override protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) { PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName); - if (pd != null) { - return new BeanPropertyHandler(pd); - } - return null; + return (pd != null ? new BeanPropertyHandler(pd) : null); } @Override @@ -238,8 +235,7 @@ protected BeanWrapperImpl newNestedPropertyAccessor(Object object, String nested @Override protected NotWritablePropertyException createNotWritablePropertyException(String propertyName) { PropertyMatches matches = PropertyMatches.forProperty(propertyName, getRootClass()); - throw new NotWritablePropertyException( - getRootClass(), getNestedPath() + propertyName, + throw new NotWritablePropertyException(getRootClass(), getNestedPath() + propertyName, matches.buildErrorMessage(), matches.getPossibleMatches()); } diff --git a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java index 2efe9dabeb13..a9a01f2bf4aa 100644 --- a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java +++ b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -339,10 +339,10 @@ Class getBeanClass() { PropertyDescriptor getPropertyDescriptor(String name) { PropertyDescriptor pd = this.propertyDescriptorCache.get(name); if (pd == null && StringUtils.hasLength(name)) { - // Same lenient fallback checking as in PropertyTypeDescriptor... - pd = this.propertyDescriptorCache.get(name.substring(0, 1).toLowerCase() + name.substring(1)); + // Same lenient fallback checking as in Property... + pd = this.propertyDescriptorCache.get(StringUtils.uncapitalize(name)); if (pd == null) { - pd = this.propertyDescriptorCache.get(name.substring(0, 1).toUpperCase() + name.substring(1)); + pd = this.propertyDescriptorCache.get(StringUtils.capitalize(name)); } } return (pd == null || pd instanceof GenericTypeAwarePropertyDescriptor ? pd : diff --git a/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java b/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java index 356231132d2a..b4777d440988 100644 --- a/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,8 +76,8 @@ protected FieldPropertyHandler getLocalPropertyHandler(String propertyName) { Field field = ReflectionUtils.findField(getWrappedClass(), propertyName); if (field != null) { propertyHandler = new FieldPropertyHandler(field); + this.fieldMap.put(propertyName, propertyHandler); } - this.fieldMap.put(propertyName, propertyHandler); } return propertyHandler; } diff --git a/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java b/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java index 1411f5569b3c..5c0942cffcd2 100644 --- a/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java +++ b/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,8 +43,10 @@ * Decorator for a standard {@link BeanInfo} object, e.g. as created by * {@link Introspector#getBeanInfo(Class)}, designed to discover and register static * and/or non-void returning setter methods. For example: + * *
  * public class Bean {
+ *
  *     private Foo foo;
  *
  *     public Foo getFoo() {
@@ -56,6 +58,7 @@
  *         return this;
  *     }
  * }
+ * * The standard JavaBeans {@code Introspector} will discover the {@code getFoo} read * method, but will bypass the {@code #setFoo(Foo)} write method, because its non-void * returning signature does not comply with the JavaBeans specification. @@ -68,6 +71,7 @@ * indexed properties are fully supported. * * @author Chris Beams + * @author Juergen Hoeller * @since 3.1 * @see #ExtendedBeanInfo(BeanInfo) * @see ExtendedBeanInfoFactory diff --git a/spring-beans/src/main/java/org/springframework/beans/Mergeable.java b/spring-beans/src/main/java/org/springframework/beans/Mergeable.java index cba64d94f89a..d3d127c0267e 100644 --- a/spring-beans/src/main/java/org/springframework/beans/Mergeable.java +++ b/spring-beans/src/main/java/org/springframework/beans/Mergeable.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,7 +41,7 @@ public interface Mergeable { * @param parent the object to merge with * @return the result of the merge operation * @throws IllegalArgumentException if the supplied parent is {@code null} - * @exception IllegalStateException if merging is not enabled for this instance + * @throws IllegalStateException if merging is not enabled for this instance * (i.e. {@code mergeEnabled} equals {@code false}). */ Object merge(Object parent); diff --git a/spring-beans/src/main/java/org/springframework/beans/MutablePropertyValues.java b/spring-beans/src/main/java/org/springframework/beans/MutablePropertyValues.java index efb4ae9bbd82..8167613a0fd9 100644 --- a/spring-beans/src/main/java/org/springframework/beans/MutablePropertyValues.java +++ b/spring-beans/src/main/java/org/springframework/beans/MutablePropertyValues.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -262,7 +262,7 @@ public PropertyValue getPropertyValue(String propertyName) { /** * Get the raw property value, if any. * @param propertyName the name to search for - * @return the raw property value, or {@code null} + * @return the raw property value, or {@code null} if none found * @since 4.0 * @see #getPropertyValue(String) * @see PropertyValue#getValue() @@ -283,11 +283,7 @@ public PropertyValues changesSince(PropertyValues old) { for (PropertyValue newPv : this.propertyValueList) { // if there wasn't an old one, add it PropertyValue pvOld = old.getPropertyValue(newPv.getName()); - if (pvOld == null) { - changes.addPropertyValue(newPv); - } - else if (!pvOld.equals(newPv)) { - // it's changed + if (pvOld == null || !pvOld.equals(newPv)) { changes.addPropertyValue(newPv); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyAccessException.java b/spring-beans/src/main/java/org/springframework/beans/PropertyAccessException.java index 68bdde7412f9..61cb509a86e6 100644 --- a/spring-beans/src/main/java/org/springframework/beans/PropertyAccessException.java +++ b/spring-beans/src/main/java/org/springframework/beans/PropertyAccessException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,6 @@ import java.beans.PropertyChangeEvent; -import org.springframework.core.ErrorCoded; - /** * Superclass for exceptions related to a property access, * such as type mismatch or invocation target exception. @@ -27,8 +25,8 @@ * @author Rod Johnson * @author Juergen Hoeller */ -@SuppressWarnings("serial") -public abstract class PropertyAccessException extends BeansException implements ErrorCoded { +@SuppressWarnings({"serial", "deprecation"}) +public abstract class PropertyAccessException extends BeansException implements org.springframework.core.ErrorCoded { private transient PropertyChangeEvent propertyChangeEvent; @@ -77,4 +75,10 @@ public Object getValue() { return (this.propertyChangeEvent != null ? this.propertyChangeEvent.getNewValue() : null); } + /** + * Return a corresponding error code for this type of exception. + */ + @Override + public abstract String getErrorCode(); + } diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyAccessor.java b/spring-beans/src/main/java/org/springframework/beans/PropertyAccessor.java index c7faf35524e3..fe9151805cfc 100644 --- a/spring-beans/src/main/java/org/springframework/beans/PropertyAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/PropertyAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -81,8 +81,6 @@ public interface PropertyAccessor { * (may be a nested path and/or an indexed/mapped property) * @return the property type for the particular property, * or {@code null} if not determinable - * @throws InvalidPropertyException if there is no such property or - * if the property isn't readable * @throws PropertyAccessException if the property was valid but the * accessor method failed */ @@ -95,8 +93,8 @@ public interface PropertyAccessor { * (may be a nested path and/or an indexed/mapped property) * @return the property type for the particular property, * or {@code null} if not determinable - * @throws InvalidPropertyException if there is no such property or - * if the property isn't readable + * @throws PropertyAccessException if the property was valid but the + * accessor method failed */ TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException; diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyDescriptorUtils.java b/spring-beans/src/main/java/org/springframework/beans/PropertyDescriptorUtils.java index 5e766690afa0..f90d718a05be 100644 --- a/spring-beans/src/main/java/org/springframework/beans/PropertyDescriptorUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/PropertyDescriptorUtils.java @@ -75,7 +75,7 @@ public static Class findPropertyType(Method readMethod, Method writeMethod) t } if (writeMethod != null) { - Class params[] = writeMethod.getParameterTypes(); + Class[] params = writeMethod.getParameterTypes(); if (params.length != 1) { throw new IntrospectionException("Bad write method arg count: " + writeMethod); } @@ -109,7 +109,7 @@ public static Class findIndexedPropertyType(String name, Class propertyTyp Class indexedPropertyType = null; if (indexedReadMethod != null) { - Class params[] = indexedReadMethod.getParameterTypes(); + Class[] params = indexedReadMethod.getParameterTypes(); if (params.length != 1) { throw new IntrospectionException("Bad indexed read method arg count: " + indexedReadMethod); } @@ -123,7 +123,7 @@ public static Class findIndexedPropertyType(String name, Class propertyTyp } if (indexedWriteMethod != null) { - Class params[] = indexedWriteMethod.getParameterTypes(); + Class[] params = indexedWriteMethod.getParameterTypes(); if (params.length != 2) { throw new IntrospectionException("Bad indexed write method arg count: " + indexedWriteMethod); } diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyMatches.java b/spring-beans/src/main/java/org/springframework/beans/PropertyMatches.java index 87cea9283053..8cfbc4a04694 100644 --- a/spring-beans/src/main/java/org/springframework/beans/PropertyMatches.java +++ b/spring-beans/src/main/java/org/springframework/beans/PropertyMatches.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,8 +30,8 @@ * Helper class for calculating property matches, according to a configurable * distance. Provide the list of potential matches and an easy way to generate * an error message. Works for both java bean properties and fields. - *

- * Mainly for use within the framework and in particular the binding facility + * + *

Mainly for use within the framework and in particular the binding facility. * * @author Alef Arendsen * @author Arjen Poutsma @@ -43,14 +43,12 @@ */ public abstract class PropertyMatches { - //--------------------------------------------------------------------- - // Static section - //--------------------------------------------------------------------- - /** Default maximum property distance: 2 */ public static final int DEFAULT_MAX_DISTANCE = 2; + // Static factory methods + /** * Create PropertyMatches for the given bean property. * @param propertyName the name of the property to find possible matches for @@ -90,9 +88,7 @@ public static PropertyMatches forField(String propertyName, Class beanClass, } - //--------------------------------------------------------------------- - // Instance section - //--------------------------------------------------------------------- + // Instance state private final String propertyName; @@ -107,18 +103,19 @@ private PropertyMatches(String propertyName, String[] possibleMatches) { this.possibleMatches = possibleMatches; } + /** * Return the name of the requested property. */ public String getPropertyName() { - return propertyName; + return this.propertyName; } /** * Return the calculated possible matches. */ public String[] getPossibleMatches() { - return possibleMatches; + return this.possibleMatches; } /** @@ -127,6 +124,9 @@ public String[] getPossibleMatches() { */ public abstract String buildErrorMessage(); + + // Implementation support for subclasses + protected void appendHintMessage(StringBuilder msg) { msg.append("Did you mean "); for (int i = 0; i < this.possibleMatches.length; i++) { @@ -150,14 +150,14 @@ else if (i == this.possibleMatches.length - 2) { * @return the distance value */ private static int calculateStringDistance(String s1, String s2) { - if (s1.length() == 0) { + if (s1.isEmpty()) { return s2.length(); } - if (s2.length() == 0) { + if (s2.isEmpty()) { return s1.length(); } - int d[][] = new int[s1.length() + 1][s2.length() + 1]; + int[][] d = new int[s1.length() + 1][s2.length() + 1]; for (int i = 0; i <= s1.length(); i++) { d[i][0] = i; } @@ -166,45 +166,46 @@ private static int calculateStringDistance(String s1, String s2) { } for (int i = 1; i <= s1.length(); i++) { - char s_i = s1.charAt(i - 1); + char c1 = s1.charAt(i - 1); for (int j = 1; j <= s2.length(); j++) { int cost; - char t_j = s2.charAt(j - 1); - if (s_i == t_j) { + char c2 = s2.charAt(j - 1); + if (c1 == c2) { cost = 0; } else { cost = 1; } - d[i][j] = Math.min(Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1), - d[i - 1][j - 1] + cost); + d[i][j] = Math.min(Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1), d[i - 1][j - 1] + cost); } } return d[s1.length()][s2.length()]; } + + // Concrete subclasses + private static class BeanPropertyMatches extends PropertyMatches { - private BeanPropertyMatches(String propertyName, Class beanClass, int maxDistance) { - super(propertyName, calculateMatches(propertyName, - BeanUtils.getPropertyDescriptors(beanClass), maxDistance)); + public BeanPropertyMatches(String propertyName, Class beanClass, int maxDistance) { + super(propertyName, + calculateMatches(propertyName, BeanUtils.getPropertyDescriptors(beanClass), maxDistance)); } /** - * Generate possible property alternatives for the given property and - * class. Internally uses the {@code getStringDistance} method, which - * in turn uses the Levenshtein algorithm to determine the distance between - * two Strings. - * @param propertyDescriptors the JavaBeans property descriptors to search + * Generate possible property alternatives for the given property and class. + * Internally uses the {@code getStringDistance} method, which in turn uses + * the Levenshtein algorithm to determine the distance between two Strings. + * @param descriptors the JavaBeans property descriptors to search * @param maxDistance the maximum distance to accept */ - private static String[] calculateMatches(String propertyName, PropertyDescriptor[] propertyDescriptors, int maxDistance) { + private static String[] calculateMatches(String name, PropertyDescriptor[] descriptors, int maxDistance) { List candidates = new ArrayList(); - for (PropertyDescriptor pd : propertyDescriptors) { + for (PropertyDescriptor pd : descriptors) { if (pd.getWriteMethod() != null) { String possibleAlternative = pd.getName(); - if (calculateStringDistance(propertyName, possibleAlternative) <= maxDistance) { + if (calculateStringDistance(name, possibleAlternative) <= maxDistance) { candidates.add(possibleAlternative); } } @@ -213,40 +214,35 @@ private static String[] calculateMatches(String propertyName, PropertyDescriptor return StringUtils.toStringArray(candidates); } - @Override public String buildErrorMessage() { - String propertyName = getPropertyName(); - String[] possibleMatches = getPossibleMatches(); - StringBuilder msg = new StringBuilder(); - msg.append("Bean property '"); - msg.append(propertyName); - msg.append("' is not writable or has an invalid setter method. "); - - if (ObjectUtils.isEmpty(possibleMatches)) { - msg.append("Does the parameter type of the setter match the return type of the getter?"); + StringBuilder msg = new StringBuilder(160); + msg.append("Bean property '").append(getPropertyName()).append( + "' is not writable or has an invalid setter method. "); + if (!ObjectUtils.isEmpty(getPossibleMatches())) { + appendHintMessage(msg); } else { - appendHintMessage(msg); + msg.append("Does the parameter type of the setter match the return type of the getter?"); } return msg.toString(); } - } + private static class FieldPropertyMatches extends PropertyMatches { - private FieldPropertyMatches(String propertyName, Class beanClass, int maxDistance) { + public FieldPropertyMatches(String propertyName, Class beanClass, int maxDistance) { super(propertyName, calculateMatches(propertyName, beanClass, maxDistance)); } - private static String[] calculateMatches(final String propertyName, Class beanClass, final int maxDistance) { + private static String[] calculateMatches(final String name, Class clazz, final int maxDistance) { final List candidates = new ArrayList(); - ReflectionUtils.doWithFields(beanClass, new ReflectionUtils.FieldCallback() { + ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() { @Override public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { String possibleAlternative = field.getName(); - if (calculateStringDistance(propertyName, possibleAlternative) <= maxDistance) { + if (calculateStringDistance(name, possibleAlternative) <= maxDistance) { candidates.add(possibleAlternative); } } @@ -255,22 +251,16 @@ public void doWith(Field field) throws IllegalArgumentException, IllegalAccessEx return StringUtils.toStringArray(candidates); } - @Override public String buildErrorMessage() { - String propertyName = getPropertyName(); - String[] possibleMatches = getPossibleMatches(); - StringBuilder msg = new StringBuilder(); - msg.append("Bean property '"); - msg.append(propertyName); - msg.append("' has no matching field. "); - - if (!ObjectUtils.isEmpty(possibleMatches)) { + StringBuilder msg = new StringBuilder(80); + msg.append("Bean property '").append(getPropertyName()).append("' has no matching field."); + if (!ObjectUtils.isEmpty(getPossibleMatches())) { + msg.append(' '); appendHintMessage(msg); } return msg.toString(); } - } } diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyValue.java b/spring-beans/src/main/java/org/springframework/beans/PropertyValue.java index 4f4f37ca792a..3c2f4e6c5b51 100644 --- a/spring-beans/src/main/java/org/springframework/beans/PropertyValue.java +++ b/spring-beans/src/main/java/org/springframework/beans/PropertyValue.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,8 +45,6 @@ public class PropertyValue extends BeanMetadataAttributeAccessor implements Seri private final Object value; - private Object source; - private boolean optional = false; private boolean converted = false; @@ -78,12 +76,12 @@ public PropertyValue(PropertyValue original) { Assert.notNull(original, "Original must not be null"); this.name = original.getName(); this.value = original.getValue(); - this.source = original.getSource(); this.optional = original.isOptional(); this.converted = original.converted; this.convertedValue = original.convertedValue; this.conversionNecessary = original.conversionNecessary; this.resolvedTokens = original.resolvedTokens; + setSource(original.getSource()); copyAttributesFrom(original); } @@ -97,10 +95,10 @@ public PropertyValue(PropertyValue original, Object newValue) { Assert.notNull(original, "Original must not be null"); this.name = original.getName(); this.value = newValue; - this.source = original; this.optional = original.isOptional(); this.conversionNecessary = original.conversionNecessary; this.resolvedTokens = original.resolvedTokens; + setSource(original); copyAttributesFrom(original); } @@ -129,16 +127,28 @@ public Object getValue() { */ public PropertyValue getOriginalPropertyValue() { PropertyValue original = this; - while (original.source instanceof PropertyValue && original.source != original) { - original = (PropertyValue) original.source; + Object source = getSource(); + while (source instanceof PropertyValue && source != original) { + original = (PropertyValue) source; + source = original.getSource(); } return original; } + /** + * Set whether this is an optional value, that is, to be ignored + * when no corresponding property exists on the target class. + * @since 3.0 + */ public void setOptional(boolean optional) { this.optional = optional; } + /** + * Return whether this is an optional value, that is, to be ignored + * when no corresponding property exists on the target class. + * @since 3.0 + */ public boolean isOptional() { return this.optional; } @@ -180,7 +190,7 @@ public boolean equals(Object other) { PropertyValue otherPv = (PropertyValue) other; return (this.name.equals(otherPv.name) && ObjectUtils.nullSafeEquals(this.value, otherPv.value) && - ObjectUtils.nullSafeEquals(this.source, otherPv.source)); + ObjectUtils.nullSafeEquals(getSource(), otherPv.getSource())); } @Override 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 908bfeafe660..ef7d44eb0660 100644 --- a/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java +++ b/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ import org.springframework.core.convert.TypeDescriptor; import org.springframework.util.ClassUtils; import org.springframework.util.NumberUtils; +import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** @@ -290,15 +291,15 @@ else if (conversionService != 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("Cannot convert value of type '").append(ClassUtils.getDescriptiveType(newValue)); + msg.append("' to required type '").append(ClassUtils.getQualifiedName(requiredType)).append("'"); if (propertyName != null) { 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("]"); + "] returned inappropriate value of type '").append( + ClassUtils.getDescriptiveType(convertedValue)).append("'"); throw new IllegalArgumentException(msg.toString()); } else { @@ -324,7 +325,7 @@ private Object attemptToConvertStringToEnum(Class requiredType, String trimme if (Enum.class == requiredType) { // target type is declared as raw enum, treat the trimmed value as .FIELD_NAME - int index = trimmedValue.lastIndexOf("."); + int index = trimmedValue.lastIndexOf('.'); if (index > - 1) { String enumType = trimmedValue.substring(0, index); String fieldName = trimmedValue.substring(index + 1); @@ -353,6 +354,7 @@ private Object attemptToConvertStringToEnum(Class requiredType, String trimme // to be checked, hence we don't return it right away. try { Field enumField = requiredType.getField(trimmedValue); + ReflectionUtils.makeAccessible(enumField); convertedValue = enumField.get(null); } catch (Throwable ex) { diff --git a/spring-beans/src/main/java/org/springframework/beans/TypeConverterSupport.java b/spring-beans/src/main/java/org/springframework/beans/TypeConverterSupport.java index ba9fc09cca0f..9be206c69432 100644 --- a/spring-beans/src/main/java/org/springframework/beans/TypeConverterSupport.java +++ b/spring-beans/src/main/java/org/springframework/beans/TypeConverterSupport.java @@ -73,7 +73,7 @@ private T doConvert(Object value, Class requiredType, MethodParameter met catch (IllegalStateException ex) { throw new ConversionNotSupportedException(value, requiredType, ex); } - catch (Throwable ex) { + catch (IllegalArgumentException ex) { throw new TypeMismatchException(value, requiredType, ex); } } 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 c2cffe10b5af..7bc7a90113c0 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-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,7 +41,7 @@ public class TypeMismatchException extends PropertyAccessException { /** - * Create a new TypeMismatchException. + * Create a new {@code TypeMismatchException}. * @param propertyChangeEvent the PropertyChangeEvent that resulted in the problem * @param requiredType the required target type */ @@ -50,17 +50,17 @@ public TypeMismatchException(PropertyChangeEvent propertyChangeEvent, Class r } /** - * Create a new TypeMismatchException. + * Create a new {@code TypeMismatchException}. * @param propertyChangeEvent the PropertyChangeEvent that resulted in the problem * @param requiredType the required target type (or {@code null} if not known) * @param cause the root cause (may be {@code null}) */ public TypeMismatchException(PropertyChangeEvent propertyChangeEvent, Class requiredType, Throwable cause) { super(propertyChangeEvent, - "Failed to convert property value of type [" + - ClassUtils.getDescriptiveType(propertyChangeEvent.getNewValue()) + "]" + + "Failed to convert property value of type '" + + ClassUtils.getDescriptiveType(propertyChangeEvent.getNewValue()) + "'" + (requiredType != null ? - " to required type [" + ClassUtils.getQualifiedName(requiredType) + "]" : "") + + " to required type '" + ClassUtils.getQualifiedName(requiredType) + "'" : "") + (propertyChangeEvent.getPropertyName() != null ? " for property '" + propertyChangeEvent.getPropertyName() + "'" : ""), cause); @@ -69,7 +69,7 @@ public TypeMismatchException(PropertyChangeEvent propertyChangeEvent, Class r } /** - * Create a new TypeMismatchException without PropertyChangeEvent. + * Create a new {@code TypeMismatchException} without a {@code PropertyChangeEvent}. * @param value the offending value that couldn't be converted (may be {@code null}) * @param requiredType the required target type (or {@code null} if not known) */ @@ -78,14 +78,14 @@ public TypeMismatchException(Object value, Class requiredType) { } /** - * Create a new TypeMismatchException without PropertyChangeEvent. + * Create a new {@code TypeMismatchException} without a {@code PropertyChangeEvent}. * @param value the offending value that couldn't be converted (may be {@code null}) * @param requiredType the required target type (or {@code null} if not known) * @param cause the root cause (may be {@code null}) */ public TypeMismatchException(Object value, Class requiredType, Throwable cause) { - super("Failed to convert value of type [" + ClassUtils.getDescriptiveType(value) + "]" + - (requiredType != null ? " to required type [" + ClassUtils.getQualifiedName(requiredType) + "]" : ""), + super("Failed to convert value of type '" + ClassUtils.getDescriptiveType(value) + "'" + + (requiredType != null ? " to required type '" + ClassUtils.getQualifiedName(requiredType) + "'" : ""), cause); this.value = value; this.requiredType = requiredType; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/Aware.java b/spring-beans/src/main/java/org/springframework/beans/factory/Aware.java index f993f1f7135a..8423a458e169 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/Aware.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/Aware.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,21 +17,19 @@ package org.springframework.beans.factory; /** - * Marker superinterface indicating that a bean is eligible to be - * notified by the Spring container of a particular framework object - * through a callback-style method. Actual method signature is - * determined by individual subinterfaces, but should typically - * consist of just one void-returning method that accepts a single - * argument. + * A marker superinterface indicating that a bean is eligible to be notified by the + * Spring container of a particular framework object through a callback-style method. + * The actual method signature is determined by individual subinterfaces but should + * typically consist of just one void-returning method that accepts a single argument. * - *

Note that merely implementing {@link Aware} provides no default - * functionality. Rather, processing must be done explicitly, for example - * in a {@link org.springframework.beans.factory.config.BeanPostProcessor BeanPostProcessor}. + *

Note that merely implementing {@link Aware} provides no default functionality. + * Rather, processing must be done explicitly, for example in a + * {@link org.springframework.beans.factory.config.BeanPostProcessor}. * Refer to {@link org.springframework.context.support.ApplicationContextAwareProcessor} - * and {@link org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory} - * for examples of processing {@code *Aware} interface callbacks. + * for an example of processing specific {@code *Aware} interface callbacks. * * @author Chris Beams + * @author Juergen Hoeller * @since 3.1 */ public interface Aware { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanDefinitionStoreException.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanDefinitionStoreException.java index 47a2ec8c14b8..1d0fc9a7f4b1 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanDefinitionStoreException.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanDefinitionStoreException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -75,7 +75,7 @@ public BeanDefinitionStoreException(String resourceDescription, String msg, Thro /** * Create a new BeanDefinitionStoreException. * @param resourceDescription description of the resource that the bean definition came from - * @param beanName the name of the bean requested + * @param beanName the name of the bean * @param msg the detail message (appended to an introductory message that indicates * the resource and the name of the bean) */ @@ -86,28 +86,28 @@ public BeanDefinitionStoreException(String resourceDescription, String beanName, /** * Create a new BeanDefinitionStoreException. * @param resourceDescription description of the resource that the bean definition came from - * @param beanName the name of the bean requested + * @param beanName the name of the bean * @param msg the detail message (appended to an introductory message that indicates * the resource and the name of the bean) * @param cause the root cause (may be {@code null}) */ public BeanDefinitionStoreException(String resourceDescription, String beanName, String msg, Throwable cause) { - super("Invalid bean definition with name '" + beanName + "' defined in " + resourceDescription + ": " + msg, cause); + super("Invalid bean definition with name '" + beanName + "' defined in " + resourceDescription + ": " + msg, + cause); this.resourceDescription = resourceDescription; this.beanName = beanName; } /** - * Return the description of the resource that the bean - * definition came from, if any. + * Return the description of the resource that the bean definition came from, if available. */ public String getResourceDescription() { return this.resourceDescription; } /** - * Return the name of the bean requested, if any. + * Return the name of the bean, if available. */ public String getBeanName() { return this.beanName; 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 58b2fd97bb67..83498c333e3d 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-2015 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,28 +63,35 @@ * are supposed to override beans of the same name in any parent factory. * *

Bean factory implementations should support the standard bean lifecycle interfaces - * as far as possible. The full set of initialization methods and their standard order is:
- * 1. BeanNameAware's {@code setBeanName}
- * 2. BeanClassLoaderAware's {@code setBeanClassLoader}
- * 3. BeanFactoryAware's {@code setBeanFactory}
- * 4. ResourceLoaderAware's {@code setResourceLoader} - * (only applicable when running in an application context)
- * 5. ApplicationEventPublisherAware's {@code setApplicationEventPublisher} - * (only applicable when running in an application context)
- * 6. MessageSourceAware's {@code setMessageSource} - * (only applicable when running in an application context)
- * 7. ApplicationContextAware's {@code setApplicationContext} - * (only applicable when running in an application context)
- * 8. ServletContextAware's {@code setServletContext} - * (only applicable when running in a web application context)
- * 9. {@code postProcessBeforeInitialization} methods of BeanPostProcessors
- * 10. InitializingBean's {@code afterPropertiesSet}
- * 11. a custom init-method definition
- * 12. {@code postProcessAfterInitialization} methods of BeanPostProcessors + * as far as possible. The full set of initialization methods and their standard order is: + *

    + *
  1. BeanNameAware's {@code setBeanName} + *
  2. BeanClassLoaderAware's {@code setBeanClassLoader} + *
  3. BeanFactoryAware's {@code setBeanFactory} + *
  4. EnvironmentAware's {@code setEnvironment} + *
  5. EmbeddedValueResolverAware's {@code setEmbeddedValueResolver} + *
  6. ResourceLoaderAware's {@code setResourceLoader} + * (only applicable when running in an application context) + *
  7. ApplicationEventPublisherAware's {@code setApplicationEventPublisher} + * (only applicable when running in an application context) + *
  8. MessageSourceAware's {@code setMessageSource} + * (only applicable when running in an application context) + *
  9. ApplicationContextAware's {@code setApplicationContext} + * (only applicable when running in an application context) + *
  10. ServletContextAware's {@code setServletContext} + * (only applicable when running in a web application context) + *
  11. {@code postProcessBeforeInitialization} methods of BeanPostProcessors + *
  12. InitializingBean's {@code afterPropertiesSet} + *
  13. a custom init-method definition + *
  14. {@code postProcessAfterInitialization} methods of BeanPostProcessors + *
* - *

On shutdown of a bean factory, the following lifecycle methods apply:
- * 1. DisposableBean's {@code destroy}
- * 2. a custom destroy-method definition + *

On shutdown of a bean factory, the following lifecycle methods apply: + *

    + *
  1. {@code postProcessBeforeDestruction} methods of DestructionAwareBeanPostProcessors + *
  2. DisposableBean's {@code destroy} + *
  3. a custom destroy-method definition + *
* * @author Rod Johnson * @author Juergen Hoeller @@ -151,23 +158,6 @@ public interface BeanFactory { */ T getBean(String name, Class requiredType) throws BeansException; - /** - * Return the bean instance that uniquely matches the given object type, if any. - * @param requiredType type the bean must match; can be an interface or superclass. - * {@code null} is disallowed. - *

This method goes into {@link ListableBeanFactory} by-type lookup territory - * but may also be translated into a conventional by-name lookup based on the name - * of the given type. For more extensive retrieval operations across sets of beans, - * use {@link ListableBeanFactory} and/or {@link BeanFactoryUtils}. - * @return an instance of the single bean matching the required type - * @throws NoSuchBeanDefinitionException if no bean of the given type was found - * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found - * @throws BeansException if the bean could not be created - * @since 3.0 - * @see ListableBeanFactory - */ - T getBean(Class requiredType) throws BeansException; - /** * Return an instance, which may be shared or independent, of the specified bean. *

Allows for specifying explicit constructor arguments / factory method arguments, @@ -184,6 +174,23 @@ public interface BeanFactory { */ Object getBean(String name, Object... args) throws BeansException; + /** + * Return the bean instance that uniquely matches the given object type, if any. + *

This method goes into {@link ListableBeanFactory} by-type lookup territory + * but may also be translated into a conventional by-name lookup based on the name + * of the given type. For more extensive retrieval operations across sets of beans, + * use {@link ListableBeanFactory} and/or {@link BeanFactoryUtils}. + * @param requiredType type the bean must match; can be an interface or superclass. + * {@code null} is disallowed. + * @return an instance of the single bean matching the required type + * @throws NoSuchBeanDefinitionException if no bean of the given type was found + * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found + * @throws BeansException if the bean could not be created + * @since 3.0 + * @see ListableBeanFactory + */ + T getBean(Class requiredType) throws BeansException; + /** * Return an instance, which may be shared or independent, of the specified bean. *

Allows for specifying explicit constructor arguments / factory method arguments, diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java index 42b749e6fa2a..7a67710ea941 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -147,14 +147,7 @@ public static String[] beanNamesForTypeIncludingAncestors(ListableBeanFactory lb if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) { String[] parentResult = beanNamesForTypeIncludingAncestors( (ListableBeanFactory) hbf.getParentBeanFactory(), type); - List resultList = new ArrayList(); - resultList.addAll(Arrays.asList(result)); - for (String beanName : parentResult) { - if (!resultList.contains(beanName) && !hbf.containsLocalBean(beanName)) { - resultList.add(beanName); - } - } - result = StringUtils.toStringArray(resultList); + result = mergeNamesWithParent(result, parentResult, hbf); } } return result; @@ -180,14 +173,7 @@ public static String[] beanNamesForTypeIncludingAncestors(ListableBeanFactory lb if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) { String[] parentResult = beanNamesForTypeIncludingAncestors( (ListableBeanFactory) hbf.getParentBeanFactory(), type); - List resultList = new ArrayList(); - resultList.addAll(Arrays.asList(result)); - for (String beanName : parentResult) { - if (!resultList.contains(beanName) && !hbf.containsLocalBean(beanName)) { - resultList.add(beanName); - } - } - result = StringUtils.toStringArray(resultList); + result = mergeNamesWithParent(result, parentResult, hbf); } } return result; @@ -223,14 +209,7 @@ public static String[] beanNamesForTypeIncludingAncestors( if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) { String[] parentResult = beanNamesForTypeIncludingAncestors( (ListableBeanFactory) hbf.getParentBeanFactory(), type, includeNonSingletons, allowEagerInit); - List resultList = new ArrayList(); - resultList.addAll(Arrays.asList(result)); - for (String beanName : parentResult) { - if (!resultList.contains(beanName) && !hbf.containsLocalBean(beanName)) { - resultList.add(beanName); - } - } - result = StringUtils.toStringArray(resultList); + result = mergeNamesWithParent(result, parentResult, hbf); } } return result; @@ -446,6 +425,29 @@ public static T beanOfType( return uniqueBean(type, beansOfType); } + + /** + * Merge the given bean names result with the given parent result. + * @param result the local bean name result + * @param parentResult the parent bean name result (possibly empty) + * @param hbf the local bean factory + * @return the merged result (possibly the local result as-is) + * @since 4.3.15 + */ + private static String[] mergeNamesWithParent(String[] result, String[] parentResult, HierarchicalBeanFactory hbf) { + if (parentResult.length == 0) { + return result; + } + List merged = new ArrayList(result.length + parentResult.length); + merged.addAll(Arrays.asList(result)); + for (String beanName : parentResult) { + if (!merged.contains(beanName) && !hbf.containsLocalBean(beanName)) { + merged.add(beanName); + } + } + return StringUtils.toStringArray(merged); + } + /** * Extract a unique bean for the given type from the given Map of matching beans. * @param type type of bean to match @@ -455,11 +457,11 @@ public static T beanOfType( * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found */ private static T uniqueBean(Class type, Map matchingBeans) { - int nrFound = matchingBeans.size(); - if (nrFound == 1) { + int count = matchingBeans.size(); + if (count == 1) { return matchingBeans.values().iterator().next(); } - else if (nrFound > 1) { + else if (count > 1) { throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet()); } else { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanNotOfRequiredTypeException.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanNotOfRequiredTypeException.java index 6f98f4353916..1cb0b75830eb 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanNotOfRequiredTypeException.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanNotOfRequiredTypeException.java @@ -17,6 +17,7 @@ package org.springframework.beans.factory; import org.springframework.beans.BeansException; +import org.springframework.util.ClassUtils; /** * Thrown when a bean doesn't match the expected type. @@ -45,8 +46,8 @@ public class BeanNotOfRequiredTypeException extends BeansException { * the expected type */ public BeanNotOfRequiredTypeException(String beanName, Class requiredType, Class actualType) { - super("Bean named '" + beanName + "' is expected to be of type [" + requiredType.getName() + - "] but was actually of type [" + actualType.getName() + "]"); + super("Bean named '" + beanName + "' is expected to be of type '" + ClassUtils.getQualifiedName(requiredType) + + "' but was actually of type '" + ClassUtils.getQualifiedName(actualType) + "'"); this.beanName = beanName; this.requiredType = requiredType; this.actualType = actualType; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/CannotLoadBeanClassException.java b/spring-beans/src/main/java/org/springframework/beans/factory/CannotLoadBeanClassException.java index 772da666e2d7..17b8f16296ea 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/CannotLoadBeanClassException.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/CannotLoadBeanClassException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,8 +46,8 @@ public class CannotLoadBeanClassException extends FatalBeanException { public CannotLoadBeanClassException( String resourceDescription, String beanName, String beanClassName, ClassNotFoundException cause) { - super("Cannot find class [" + beanClassName + "] for bean with name '" + beanName + - "' defined in " + resourceDescription, cause); + super("Cannot find class [" + beanClassName + "] for bean with name '" + beanName + "'" + + (resourceDescription != null ? " defined in " + resourceDescription : ""), cause); this.resourceDescription = resourceDescription; this.beanName = beanName; this.beanClassName = beanClassName; @@ -64,8 +64,9 @@ public CannotLoadBeanClassException( public CannotLoadBeanClassException( String resourceDescription, String beanName, String beanClassName, LinkageError cause) { - super("Error loading class [" + beanClassName + "] for bean with name '" + beanName + - "' defined in " + resourceDescription + ": problem with class file or dependent class", cause); + super("Error loading class [" + beanClassName + "] for bean with name '" + beanName + "'" + + (resourceDescription != null ? " defined in " + resourceDescription : "") + + ": problem with class file or dependent class", cause); this.resourceDescription = resourceDescription; this.beanName = beanName; this.beanClassName = beanClassName; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/DisposableBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/DisposableBean.java index 5554afc2b3d7..d92f7ed6a2f8 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/DisposableBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/DisposableBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,27 +17,29 @@ package org.springframework.beans.factory; /** - * Interface to be implemented by beans that want to release resources - * on destruction. A BeanFactory is supposed to invoke the destroy - * method if it disposes a cached singleton. An application context - * is supposed to dispose all of its singletons on close. + * Interface to be implemented by beans that want to release resources on destruction. + * A {@link BeanFactory} will invoke the destroy method on individual destruction of a + * scoped bean. An {@link org.springframework.context.ApplicationContext} is supposed + * to dispose all of its singletons on shutdown, driven by the application lifecycle. * - *

An alternative to implementing DisposableBean is specifying a custom - * destroy-method, for example in an XML bean definition. - * For a list of all bean lifecycle methods, see the BeanFactory javadocs. + *

A Spring-managed bean may also implement Java's {@link AutoCloseable} interface + * for the same purpose. An alternative to implementing an interface is specifying a + * custom destroy method, for example in an XML bean definition. For a list of all + * bean lifecycle methods, see the {@link BeanFactory BeanFactory javadocs}. * * @author Juergen Hoeller * @since 12.08.2003 - * @see org.springframework.beans.factory.support.RootBeanDefinition#getDestroyMethodName - * @see org.springframework.context.ConfigurableApplicationContext#close + * @see InitializingBean + * @see org.springframework.beans.factory.support.RootBeanDefinition#getDestroyMethodName() + * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#destroySingletons() + * @see org.springframework.context.ConfigurableApplicationContext#close() */ public interface DisposableBean { /** - * Invoked by a BeanFactory on destruction of a singleton. - * @throws Exception in case of shutdown errors. - * Exceptions will get logged but not rethrown to allow - * other beans to release their resources too. + * Invoked by the containing {@code BeanFactory} on destruction of a bean. + * @throws Exception in case of shutdown errors. Exceptions will get logged + * but not rethrown to allow other beans to release their resources as well. */ void destroy() throws Exception; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/FactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/FactoryBean.java index 84763c37f6c3..ab465931aba1 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/FactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/FactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,31 +17,34 @@ package org.springframework.beans.factory; /** - * Interface to be implemented by objects used within a {@link BeanFactory} - * which are themselves factories. If a bean implements this interface, - * it is used as a factory for an object to expose, not directly as a bean - * instance that will be exposed itself. + * Interface to be implemented by objects used within a {@link BeanFactory} which + * are themselves factories for individual objects. If a bean implements this + * interface, it is used as a factory for an object to expose, not directly as a + * bean instance that will be exposed itself. * - *

NB: A bean that implements this interface cannot be used as a - * normal bean. A FactoryBean is defined in a bean style, but the - * object exposed for bean references ({@link #getObject()} is always - * the object that it creates. + *

NB: A bean that implements this interface cannot be used as a normal bean. + * A FactoryBean is defined in a bean style, but the object exposed for bean + * references ({@link #getObject()}) is always the object that it creates. * - *

FactoryBeans can support singletons and prototypes, and can - * either create objects lazily on demand or eagerly on startup. - * The {@link SmartFactoryBean} interface allows for exposing - * more fine-grained behavioral metadata. + *

FactoryBeans can support singletons and prototypes, and can either create + * objects lazily on demand or eagerly on startup. The {@link SmartFactoryBean} + * interface allows for exposing more fine-grained behavioral metadata. * - *

This interface is heavily used within the framework itself, for - * example for the AOP {@link org.springframework.aop.framework.ProxyFactoryBean} - * or the {@link org.springframework.jndi.JndiObjectFactoryBean}. - * It can be used for application components as well; however, - * this is not common outside of infrastructure code. + *

This interface is heavily used within the framework itself, for example for + * the AOP {@link org.springframework.aop.framework.ProxyFactoryBean} or the + * {@link org.springframework.jndi.JndiObjectFactoryBean}. It can be used for + * custom components as well; however, this is only common for infrastructure code. * - *

NOTE: FactoryBean objects participate in the containing - * BeanFactory's synchronization of bean creation. There is usually no - * need for internal synchronization other than for purposes of lazy - * initialization within the FactoryBean itself (or the like). + *

{@code FactoryBean} is a programmatic contract. Implementations are not + * supposed to rely on annotation-driven injection or other reflective facilities. + * {@link #getObjectType()} {@link #getObject()} invocations may arrive early in + * the bootstrap process, even ahead of any post-processor setup. If you need access + * other beans, implement {@link BeanFactoryAware} and obtain them programmatically. + * + *

Finally, FactoryBean objects participate in the containing BeanFactory's + * synchronization of bean creation. There is usually no need for internal + * synchronization other than for purposes of lazy initialization within the + * FactoryBean itself (or the like). * * @author Rod Johnson * @author Juergen Hoeller diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/InitializingBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/InitializingBean.java index 365bf99bf1ab..b0ca094510e3 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/InitializingBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/InitializingBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,31 +17,29 @@ package org.springframework.beans.factory; /** - * Interface to be implemented by beans that need to react once all their - * properties have been set by a BeanFactory: for example, to perform custom - * initialization, or merely to check that all mandatory properties have been set. + * Interface to be implemented by beans that need to react once all their properties + * have been set by a {@link BeanFactory}: e.g. to perform custom initialization, + * or merely to check that all mandatory properties have been set. * - *

An alternative to implementing InitializingBean is specifying a custom - * init-method, for example in an XML bean definition. - * For a list of all bean lifecycle methods, see the BeanFactory javadocs. + *

An alternative to implementing {@code InitializingBean} is specifying a custom + * init method, for example in an XML bean definition. For a list of all bean + * lifecycle methods, see the {@link BeanFactory BeanFactory javadocs}. * * @author Rod Johnson - * @see BeanNameAware - * @see BeanFactoryAware - * @see BeanFactory - * @see org.springframework.beans.factory.support.RootBeanDefinition#getInitMethodName - * @see org.springframework.context.ApplicationContextAware + * @author Juergen Hoeller + * @see DisposableBean + * @see org.springframework.beans.factory.config.BeanDefinition#getPropertyValues() + * @see org.springframework.beans.factory.support.AbstractBeanDefinition#getInitMethodName() */ public interface InitializingBean { /** - * Invoked by a BeanFactory after it has set all bean properties supplied - * (and satisfied BeanFactoryAware and ApplicationContextAware). - *

This method allows the bean instance to perform initialization only - * possible when all bean properties have been set and to throw an - * exception in the event of misconfiguration. - * @throws Exception in the event of misconfiguration (such - * as failure to set an essential property) or if initialization fails. + * Invoked by the containing {@code BeanFactory} after it has set all bean properties + * and satisfied {@link BeanFactoryAware}, {@code ApplicationContextAware} etc. + *

This method allows the bean instance to perform validation of its overall + * configuration and final initialization when all bean properties have been set. + * @throws Exception in the event of misconfiguration (such as failure to set an + * essential property) or if initialization fails for any other reason */ void afterPropertiesSet() throws Exception; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java b/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java index 0a3d55a9312a..38ace23fd40b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -111,6 +111,17 @@ public Annotation[] getAnnotations() { } } + /** + * Retrieve a field/parameter annotation of the given type, if any. + * @param annotationType the annotation type to retrieve + * @return the annotation instance, or {@code null} if none found + * @since 4.3.9 + */ + public A getAnnotation(Class annotationType) { + return (this.field != null ? this.field.getAnnotation(annotationType) : + this.methodParameter.getParameterAnnotation(annotationType)); + } + /** * Return the type declared by the underlying field or method/constructor parameter, * indicating the injection type. 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 e8be3322896c..1c0c70b46f10 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-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -243,7 +243,9 @@ Map getBeansOfType(Class type, boolean includeNonSingletons, b /** * Find all names of beans whose {@code Class} has the supplied {@link Annotation} - * type, without creating any bean instances yet. + * type, without creating corresponding bean instances yet. + *

Note that this method considers objects created by FactoryBeans, which means + * that FactoryBeans will get initialized in order to determine their object type. * @param annotationType the type of annotation to look for * @return the names of all matching beans * @since 4.0 @@ -253,6 +255,8 @@ Map getBeansOfType(Class type, boolean includeNonSingletons, b /** * Find all beans whose {@code Class} has the supplied {@link Annotation} type, * returning a Map of bean names with corresponding bean instances. + *

Note that this method considers objects created by FactoryBeans, which means + * that FactoryBeans will get initialized in order to determine their object type. * @param annotationType the type of annotation to look for * @return a Map with the matching beans, containing the bean names as * keys and the corresponding bean instances as values @@ -267,7 +271,7 @@ Map getBeansOfType(Class type, boolean includeNonSingletons, b * found on the given class itself. * @param beanName the name of the bean to look for annotations on * @param annotationType the annotation class to look for - * @return the annotation of the given type if found, or {@code null} + * @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 3.0 */ diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/NamedBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/NamedBean.java index e1e5ed33f572..9f2453ff6532 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/NamedBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/NamedBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2006 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,10 @@ package org.springframework.beans.factory; /** - * Counterpart of BeanNameAware. Returns the bean name of an object. + * Counterpart of {@link BeanNameAware}. Returns the bean name of an object. * - *

This interface can be introduced to avoid a brittle dependence - * on bean name in objects used with Spring IoC and Spring AOP. + *

This interface can be introduced to avoid a brittle dependence on + * bean name in objects used with Spring IoC and Spring AOP. * * @author Rod Johnson * @since 2.0 @@ -29,7 +29,7 @@ public interface NamedBean { /** - * Return the name of this bean in a Spring bean factory. + * Return the name of this bean in a Spring bean factory, if known. */ String getBeanName(); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/NoSuchBeanDefinitionException.java b/spring-beans/src/main/java/org/springframework/beans/factory/NoSuchBeanDefinitionException.java index 106fa0cafc9c..c02210581b69 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/NoSuchBeanDefinitionException.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/NoSuchBeanDefinitionException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,8 @@ package org.springframework.beans.factory; import org.springframework.beans.BeansException; +import org.springframework.core.ResolvableType; +import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** @@ -26,6 +28,7 @@ * * @author Rod Johnson * @author Juergen Hoeller + * @author Stephane Nicoll * @see BeanFactory#getBean(String) * @see BeanFactory#getBean(Class) * @see NoUniqueBeanDefinitionException @@ -33,11 +36,9 @@ @SuppressWarnings("serial") public class NoSuchBeanDefinitionException extends BeansException { - /** Name of the missing bean */ private String beanName; - /** Required type of the missing bean */ - private Class beanType; + private ResolvableType resolvableType; /** @@ -45,7 +46,7 @@ public class NoSuchBeanDefinitionException extends BeansException { * @param name the name of the missing bean */ public NoSuchBeanDefinitionException(String name) { - super("No bean named '" + name + "' is defined"); + super("No bean named '" + name + "' available"); this.beanName = name; } @@ -55,7 +56,7 @@ public NoSuchBeanDefinitionException(String name) { * @param message detailed message describing the problem */ public NoSuchBeanDefinitionException(String name, String message) { - super("No bean named '" + name + "' is defined: " + message); + super("No bean named '" + name + "' available: " + message); this.beanName = name; } @@ -64,8 +65,7 @@ public NoSuchBeanDefinitionException(String name, String message) { * @param type required type of the missing bean */ public NoSuchBeanDefinitionException(Class type) { - super("No qualifying bean of type [" + type.getName() + "] is defined"); - this.beanType = type; + this(ResolvableType.forClass(type)); } /** @@ -74,8 +74,28 @@ public NoSuchBeanDefinitionException(Class type) { * @param message detailed message describing the problem */ public NoSuchBeanDefinitionException(Class type, String message) { - super("No qualifying bean of type [" + type.getName() + "] is defined: " + message); - this.beanType = type; + this(ResolvableType.forClass(type), message); + } + + /** + * Create a new {@code NoSuchBeanDefinitionException}. + * @param type full type declaration of the missing bean + * @since 4.3.4 + */ + public NoSuchBeanDefinitionException(ResolvableType type) { + super("No qualifying bean of type '" + type + "' available"); + this.resolvableType = type; + } + + /** + * Create a new {@code NoSuchBeanDefinitionException}. + * @param type full type declaration of the missing bean + * @param message detailed message describing the problem + * @since 4.3.4 + */ + public NoSuchBeanDefinitionException(ResolvableType type, String message) { + super("No qualifying bean of type '" + type + "' available: " + message); + this.resolvableType = type; } /** @@ -83,12 +103,15 @@ public NoSuchBeanDefinitionException(Class type, String message) { * @param type required type of the missing bean * @param dependencyDescription a description of the originating dependency * @param message detailed message describing the problem + * @deprecated as of 4.3.4, in favor of {@link #NoSuchBeanDefinitionException(ResolvableType, String)} */ + @Deprecated public NoSuchBeanDefinitionException(Class type, String dependencyDescription, String message) { - super("No qualifying bean of type [" + type.getName() + "] found for dependency" + + super("No qualifying bean" + (!StringUtils.hasLength(dependencyDescription) ? + " of type '" + ClassUtils.getQualifiedName(type) + "'" : "") + " found for dependency" + (StringUtils.hasLength(dependencyDescription) ? " [" + dependencyDescription + "]" : "") + ": " + message); - this.beanType = type; + this.resolvableType = ResolvableType.forClass(type); } @@ -100,10 +123,20 @@ public String getBeanName() { } /** - * Return the required type of the missing bean, if it was a lookup by type that failed. + * Return the required type of the missing bean, if it was a lookup by type + * that failed. */ public Class getBeanType() { - return this.beanType; + return (this.resolvableType != null ? this.resolvableType.resolve() : null); + } + + /** + * Return the required {@link ResolvableType} of the missing bean, if it was a lookup + * by type that failed. + * @since 4.3.4 + */ + public ResolvableType getResolvableType() { + return this.resolvableType; } /** diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/ObjectFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/ObjectFactory.java index 40f807dd543d..9b923624855f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/ObjectFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/ObjectFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,7 @@ public interface ObjectFactory { /** * Return an instance (possibly shared or independent) * of the object managed by this factory. - * @return an instance of the bean (should never be {@code null}) + * @return the resulting instance * @throws BeansException in case of creation errors */ T getObject() throws BeansException; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java b/spring-beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java index 0403abfc7a9a..666415dc642c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java @@ -18,6 +18,7 @@ import org.springframework.beans.BeansException; import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; /** * Exception thrown when a bean depends on other beans or simple properties @@ -46,7 +47,7 @@ public UnsatisfiedDependencyException( super(resourceDescription, beanName, "Unsatisfied dependency expressed through bean property '" + propertyName + "'" + - (msg != null ? ": " + msg : "")); + (StringUtils.hasLength(msg) ? ": " + msg : "")); } /** @@ -59,7 +60,7 @@ public UnsatisfiedDependencyException( public UnsatisfiedDependencyException( String resourceDescription, String beanName, String propertyName, BeansException ex) { - this(resourceDescription, beanName, propertyName, (ex != null ? ex.getMessage() : "")); + this(resourceDescription, beanName, propertyName, ""); initCause(ex); } @@ -74,7 +75,9 @@ public UnsatisfiedDependencyException( public UnsatisfiedDependencyException( String resourceDescription, String beanName, InjectionPoint injectionPoint, String msg) { - super(resourceDescription, beanName, "Unsatisfied dependency expressed through " + injectionPoint + ": " + msg); + super(resourceDescription, beanName, + "Unsatisfied dependency expressed through " + injectionPoint + + (StringUtils.hasLength(msg) ? ": " + msg : "")); this.injectionPoint = injectionPoint; } @@ -89,7 +92,7 @@ public UnsatisfiedDependencyException( public UnsatisfiedDependencyException( String resourceDescription, String beanName, InjectionPoint injectionPoint, BeansException ex) { - this(resourceDescription, beanName, injectionPoint, (ex != null ? ex.getMessage() : "")); + this(resourceDescription, beanName, injectionPoint, ""); initCause(ex); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AnnotationBeanWiringInfoResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AnnotationBeanWiringInfoResolver.java index 78fe8fe86af1..e8e20ab05745 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AnnotationBeanWiringInfoResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AnnotationBeanWiringInfoResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,24 +44,23 @@ public BeanWiringInfo resolveWiringInfo(Object beanInstance) { } /** - * Build the BeanWiringInfo for the given Configurable annotation. + * Build the {@link BeanWiringInfo} for the given {@link Configurable} annotation. * @param beanInstance the bean instance * @param annotation the Configurable annotation found on the bean class * @return the resolved BeanWiringInfo */ protected BeanWiringInfo buildWiringInfo(Object beanInstance, Configurable annotation) { if (!Autowire.NO.equals(annotation.autowire())) { + // Autowiring by name or by type return new BeanWiringInfo(annotation.autowire().value(), annotation.dependencyCheck()); } + else if (!"".equals(annotation.value())) { + // Explicitly specified bean name for bean definition to take property values from + return new BeanWiringInfo(annotation.value(), false); + } else { - if (!"".equals(annotation.value())) { - // explicitly specified bean name - return new BeanWiringInfo(annotation.value(), false); - } - else { - // default bean name - return new BeanWiringInfo(getDefaultBeanName(beanInstance), true); - } + // Default bean name for bean definition to take property values from + return new BeanWiringInfo(getDefaultBeanName(beanInstance), true); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/Autowired.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/Autowired.java index 73779365e0bc..7df2fe0f6ab7 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/Autowired.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/Autowired.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,29 +23,40 @@ import java.lang.annotation.Target; /** - * Marks a constructor, field, setter method or config method as to be - * autowired by Spring's dependency injection facilities. + * Marks a constructor, field, setter method or config method as to be autowired by + * Spring's dependency injection facilities. This is an alternative to the JSR-330 + * {@link javax.inject.Inject} annotation, adding required-vs-optional semantics. * - *

Only one constructor (at max) of any given bean class may carry this - * annotation, indicating the constructor to autowire when used as a Spring - * bean. Such a constructor does not have to be public. + *

Only one constructor (at max) of any given bean class may declare this annotation + * with the 'required' parameter set to {@code true}, indicating the constructor + * to autowire when used as a Spring bean. If multiple non-required constructors + * declare the annotation, they will be considered as candidates for autowiring. + * The constructor with the greatest number of dependencies that can be satisfied by + * matching beans in the Spring container will be chosen. If none of the candidates + * can be satisfied, then a standard default constructor (if present) will be used. + * If a class only declares a single constructor to begin with, it will always be used, + * even if not annotated. An annotated constructor does not have to be public. * - *

Fields are injected right after construction of a bean, before any - * config methods are invoked. Such a config field does not have to be public. + *

Fields are injected right after construction of a bean, before any config methods + * are invoked. Such a config field does not have to be public. * - *

Config methods may have an arbitrary name and any number of arguments; - * each of those arguments will be autowired with a matching bean in the - * Spring container. Bean property setter methods are effectively just - * a special case of such a general config method. Such config methods - * do not have to be public. + *

Config methods may have an arbitrary name and any number of arguments; each of + * those arguments will be autowired with a matching bean in the Spring container. + * Bean property setter methods are effectively just a special case of such a general + * config method. Such config methods do not have to be public. * - *

In the case of multiple argument methods, the 'required' parameter is - * applicable for all arguments. + *

In the case of a multi-arg constructor or method, the 'required' parameter is + * applicable to all arguments. Individual parameters may be declared as Java-8-style + * {@link java.util.Optional}, overriding the base required semantics. * - *

In case of a {@link java.util.Collection} or {@link java.util.Map} - * dependency type, the container will autowire all beans matching the - * declared value type. In case of a Map, the keys must be declared as - * type String and will be resolved to the corresponding bean names. + *

In case of a {@link java.util.Collection} or {@link java.util.Map} dependency type, + * the container autowires all beans matching the declared value type. For such purposes, + * the map keys must be declared as type String which will be resolved to the corresponding + * bean names. Such a container-provided collection will be ordered, taking into account + * {@link org.springframework.core.Ordered}/{@link org.springframework.core.annotation.Order} + * values of the target components, otherwise following their registration order in the + * container. Alternatively, a single matching target bean may also be a generally typed + * {@code Collection} or {@code Map} itself, getting injected as such. * *

Note that actual injection is performed through a * {@link org.springframework.beans.factory.config.BeanPostProcessor 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 258f66bf4d22..b0e8c609ccd7 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-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -74,15 +74,15 @@ *

Also supports JSR-330's {@link javax.inject.Inject @Inject} annotation, * if available, as a direct alternative to Spring's own {@code @Autowired}. * - *

Only one constructor (at max) of any given bean class may carry this - * annotation with the 'required' parameter set to {@code true}, - * indicating the constructor to autowire when used as a Spring bean. - * If multiple non-required constructors carry the annotation, they - * will be considered as candidates for autowiring. The constructor with - * the greatest number of dependencies that can be satisfied by matching - * beans in the Spring container will be chosen. If none of the candidates - * can be satisfied, then a default constructor (if present) will be used. - * An annotated constructor does not have to be public. + *

Only one constructor (at max) of any given bean class may declare this annotation + * with the 'required' parameter set to {@code true}, indicating the constructor + * to autowire when used as a Spring bean. If multiple non-required constructors + * declare the annotation, they will be considered as candidates for autowiring. + * The constructor with the greatest number of dependencies that can be satisfied by + * matching beans in the Spring container will be chosen. If none of the candidates + * can be satisfied, then a standard default constructor (if present) will be used. + * If a class only declares a single constructor to begin with, it will always be used, + * even if not annotated. An annotated constructor does not have to be public. * *

Fields are injected right after construction of a bean, before any * config methods are invoked. Such a config field does not have to be public. @@ -120,7 +120,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean protected final Log logger = LogFactory.getLog(getClass()); private final Set> autowiredAnnotationTypes = - new LinkedHashSet>(); + new LinkedHashSet>(4); private String requiredParameterName = "required"; @@ -163,11 +163,11 @@ public AutowiredAnnotationBeanPostProcessor() { /** * Set the 'autowired' annotation type, to be used on constructors, fields, * setter methods and arbitrary config methods. - *

The default autowired annotation type is the Spring-provided - * {@link Autowired} annotation, as well as {@link Value}. + *

The default autowired annotation type is the Spring-provided {@link Autowired} + * annotation, as well as {@link Value}. *

This setter property exists so that developers can provide their own - * (non-Spring-specific) annotation type to indicate that a member is - * supposed to be autowired. + * (non-Spring-specific) annotation type to indicate that a member is supposed + * to be autowired. */ public void setAutowiredAnnotationType(Class autowiredAnnotationType) { Assert.notNull(autowiredAnnotationType, "'autowiredAnnotationType' must not be null"); @@ -178,11 +178,11 @@ public void setAutowiredAnnotationType(Class autowiredAnno /** * Set the 'autowired' annotation types, to be used on constructors, fields, * setter methods and arbitrary config methods. - *

The default autowired annotation type is the Spring-provided - * {@link Autowired} annotation, as well as {@link Value}. + *

The default autowired annotation type is the Spring-provided {@link Autowired} + * annotation, as well as {@link Value}. *

This setter property exists so that developers can provide their own - * (non-Spring-specific) annotation types to indicate that a member is - * supposed to be autowired. + * (non-Spring-specific) annotation types to indicate that a member is supposed + * to be autowired. */ public void setAutowiredAnnotationTypes(Set> autowiredAnnotationTypes) { Assert.notEmpty(autowiredAnnotationTypes, "'autowiredAnnotationTypes' must not be empty"); @@ -191,8 +191,7 @@ public void setAutowiredAnnotationTypes(Set> autowir } /** - * Set the name of a parameter of the annotation that specifies - * whether it is required. + * Set the name of a parameter of the annotation that specifies whether it is required. * @see #setRequiredParameterValue(boolean) */ public void setRequiredParameterName(String requiredParameterName) { @@ -201,9 +200,8 @@ public void setRequiredParameterName(String requiredParameterName) { /** * Set the boolean value that marks a dependency as required - *

For example if using 'required=true' (the default), - * this value should be {@code true}; but if using - * 'optional=false', this value should be {@code false}. + *

For example if using 'required=true' (the default), this value should be + * {@code true}; but if using 'optional=false', this value should be {@code false}. * @see #setRequiredParameterName(String) */ public void setRequiredParameterValue(boolean requiredParameterValue) { @@ -220,10 +218,10 @@ public int getOrder() { } @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + public void setBeanFactory(BeanFactory beanFactory) { if (!(beanFactory instanceof ConfigurableListableBeanFactory)) { throw new IllegalArgumentException( - "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory"); + "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory); } this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; } @@ -238,35 +236,56 @@ public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, C } @Override - public Constructor[] determineCandidateConstructors(Class beanClass, final String beanName) throws BeansException { + public Constructor[] determineCandidateConstructors(Class beanClass, final String beanName) + throws BeanCreationException { + + // Let's check for lookup methods here.. if (!this.lookupMethodsChecked.contains(beanName)) { - ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() { - @Override - public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { - Lookup lookup = method.getAnnotation(Lookup.class); - if (lookup != null) { - LookupOverride override = new LookupOverride(method, lookup.value()); - try { - RootBeanDefinition mbd = (RootBeanDefinition) beanFactory.getMergedBeanDefinition(beanName); - mbd.getMethodOverrides().addOverride(override); - } - catch (NoSuchBeanDefinitionException ex) { - throw new BeanCreationException(beanName, - "Cannot apply @Lookup to beans without corresponding bean definition"); + try { + ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() { + @Override + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + Lookup lookup = method.getAnnotation(Lookup.class); + if (lookup != null) { + LookupOverride override = new LookupOverride(method, lookup.value()); + try { + RootBeanDefinition mbd = (RootBeanDefinition) beanFactory.getMergedBeanDefinition(beanName); + mbd.getMethodOverrides().addOverride(override); + } + catch (NoSuchBeanDefinitionException ex) { + throw new BeanCreationException(beanName, + "Cannot apply @Lookup to beans without corresponding bean definition"); + } } } - } - }); + }); + } + catch (IllegalStateException ex) { + throw new BeanCreationException(beanName, "Lookup method resolution failed", ex); + } + catch (NoClassDefFoundError err) { + throw new BeanCreationException(beanName, "Failed to introspect bean class [" + beanClass.getName() + + "] for lookup method metadata: could not find class that it depends on", err); + } this.lookupMethodsChecked.add(beanName); } // Quick check on the concurrent map first, with minimal locking. Constructor[] candidateConstructors = this.candidateConstructorsCache.get(beanClass); if (candidateConstructors == null) { + // Fully synchronized resolution now... synchronized (this.candidateConstructorsCache) { candidateConstructors = this.candidateConstructorsCache.get(beanClass); if (candidateConstructors == null) { - Constructor[] rawCandidates = beanClass.getDeclaredConstructors(); + Constructor[] rawCandidates; + try { + rawCandidates = beanClass.getDeclaredConstructors(); + } + catch (Throwable ex) { + throw new BeanCreationException(beanName, + "Resolution of declared constructors on bean Class [" + beanClass.getName() + + "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex); + } List> candidates = new ArrayList>(rawCandidates.length); Constructor requiredConstructor = null; Constructor defaultConstructor = null; @@ -292,10 +311,6 @@ public void doWith(Method method) throws IllegalArgumentException, IllegalAccess ". Found constructor with 'required' Autowired annotation already: " + requiredConstructor); } - if (candidate.getParameterTypes().length == 0) { - throw new IllegalStateException( - "Autowired annotation requires at least one argument: " + candidate); - } boolean required = determineRequiredStatus(ann); if (required) { if (!candidates.isEmpty()) { @@ -320,9 +335,9 @@ else if (candidate.getParameterTypes().length == 0) { } else if (candidates.size() == 1 && logger.isWarnEnabled()) { logger.warn("Inconsistent constructor declaration on bean with name '" + beanName + - "': single autowire-marked constructor flagged as optional - this constructor " + - "is effectively required since there is no default constructor to fall back to: " + - candidates.get(0)); + "': single autowire-marked constructor flagged as optional - " + + "this constructor is effectively required since there is no " + + "default constructor to fall back to: " + candidates.get(0)); } } candidateConstructors = candidates.toArray(new Constructor[candidates.size()]); @@ -342,7 +357,7 @@ else if (rawCandidates.length == 1 && rawCandidates[0].getParameterTypes().lengt @Override public PropertyValues postProcessPropertyValues( - PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { + PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException { InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs); try { @@ -361,9 +376,9 @@ public PropertyValues postProcessPropertyValues( * 'Native' processing method for direct calls with an arbitrary target instance, * resolving all of its fields and methods which are annotated with {@code @Autowired}. * @param bean the target instance to process - * @throws BeansException if autowiring failed + * @throws BeanCreationException if autowiring failed */ - public void processInjection(Object bean) throws BeansException { + public void processInjection(Object bean) throws BeanCreationException { Class clazz = bean.getClass(); InjectionMetadata metadata = findAutowiringMetadata(clazz.getName(), clazz, null); try { @@ -373,7 +388,8 @@ public void processInjection(Object bean) throws BeansException { throw ex; } catch (Throwable ex) { - throw new BeanCreationException("Injection of autowired dependencies failed for class [" + clazz + "]", ex); + throw new BeanCreationException( + "Injection of autowired dependencies failed for class [" + clazz + "]", ex); } } @@ -446,7 +462,8 @@ public void doWith(Method method) throws IllegalArgumentException, IllegalAccess } if (method.getParameterTypes().length == 0) { if (logger.isWarnEnabled()) { - logger.warn("Autowired annotation should be used on methods with parameters: " + method); + logger.warn("Autowired annotation should only be used on methods with parameters: " + + method); } } boolean required = determineRequiredStatus(ann); @@ -465,7 +482,7 @@ public void doWith(Method method) throws IllegalArgumentException, IllegalAccess } private AnnotationAttributes findAutowiredAnnotation(AccessibleObject ao) { - if (ao.getAnnotations().length > 0) { + if (ao.getAnnotations().length > 0) { // autowiring annotations have to be local for (Class type : this.autowiredAnnotationTypes) { AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(ao, type); if (attributes != null) { @@ -575,11 +592,10 @@ protected void inject(Object bean, String beanName, PropertyValues pvs) throws T registerDependentBeans(beanName, autowiredBeanNames); if (autowiredBeanNames.size() == 1) { String autowiredBeanName = autowiredBeanNames.iterator().next(); - if (beanFactory.containsBean(autowiredBeanName)) { - if (beanFactory.isTypeMatch(autowiredBeanName, field.getType())) { - this.cachedFieldValue = new ShortcutDependencyDescriptor( - desc, autowiredBeanName, field.getType()); - } + if (beanFactory.containsBean(autowiredBeanName) && + beanFactory.isTypeMatch(autowiredBeanName, field.getType())) { + this.cachedFieldValue = new ShortcutDependencyDescriptor( + desc, autowiredBeanName, field.getType()); } } } @@ -629,7 +645,7 @@ protected void inject(Object bean, String beanName, PropertyValues pvs) throws T Class[] paramTypes = method.getParameterTypes(); arguments = new Object[paramTypes.length]; DependencyDescriptor[] descriptors = new DependencyDescriptor[paramTypes.length]; - Set autowiredBeanNames = new LinkedHashSet(paramTypes.length); + Set autowiredBeans = new LinkedHashSet(paramTypes.length); TypeConverter typeConverter = beanFactory.getTypeConverter(); for (int i = 0; i < arguments.length; i++) { MethodParameter methodParam = new MethodParameter(method, i); @@ -637,7 +653,7 @@ protected void inject(Object bean, String beanName, PropertyValues pvs) throws T currDesc.setContainingClass(bean.getClass()); descriptors[i] = currDesc; try { - Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeanNames, typeConverter); + Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter); if (arg == null && !this.required) { arguments = null; break; @@ -655,9 +671,9 @@ protected void inject(Object bean, String beanName, PropertyValues pvs) throws T for (int i = 0; i < arguments.length; i++) { this.cachedMethodArguments[i] = descriptors[i]; } - registerDependentBeans(beanName, autowiredBeanNames); - if (autowiredBeanNames.size() == paramTypes.length) { - Iterator it = autowiredBeanNames.iterator(); + registerDependentBeans(beanName, autowiredBeans); + if (autowiredBeans.size() == paramTypes.length) { + Iterator it = autowiredBeans.iterator(); for (int i = 0; i < paramTypes.length; i++) { String autowiredBeanName = it.next(); if (beanFactory.containsBean(autowiredBeanName)) { @@ -706,19 +722,19 @@ private Object[] resolveCachedArguments(String beanName) { @SuppressWarnings("serial") private static class ShortcutDependencyDescriptor extends DependencyDescriptor { - private final String shortcutName; + private final String shortcut; private final Class requiredType; - public ShortcutDependencyDescriptor(DependencyDescriptor original, String shortcutName, Class requiredType) { + public ShortcutDependencyDescriptor(DependencyDescriptor original, String shortcut, Class requiredType) { super(original); - this.shortcutName = shortcutName; + this.shortcut = shortcut; this.requiredType = requiredType; } @Override public Object resolveShortcut(BeanFactory beanFactory) { - return resolveCandidate(this.shortcutName, this.requiredType, beanFactory); + return resolveCandidate(this.shortcut, this.requiredType, beanFactory); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/BeanFactoryAnnotationUtils.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/BeanFactoryAnnotationUtils.java index 65901004885c..acc87d64950c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/BeanFactoryAnnotationUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/BeanFactoryAnnotationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,27 +18,30 @@ import java.lang.reflect.Method; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.AutowireCandidateQualifier; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** * Convenience methods performing bean lookups related to annotations, for example * Spring's {@link Qualifier @Qualifier} annotation. * - * @author Chris Beams * @author Juergen Hoeller + * @author Chris Beams * @since 3.1.2 * @see BeanFactoryUtils */ -public class BeanFactoryAnnotationUtils { +public abstract class BeanFactoryAnnotationUtils { /** * Obtain a bean of type {@code T} from the given {@code BeanFactory} declaring a @@ -48,9 +51,16 @@ public class BeanFactoryAnnotationUtils { * @param beanType the type of bean to retrieve * @param qualifier the qualifier for selecting between multiple bean matches * @return the matching bean of type {@code T} (never {@code null}) + * @throws NoUniqueBeanDefinitionException if multiple matching beans of type {@code T} found * @throws NoSuchBeanDefinitionException if no matching bean of type {@code T} found + * @throws BeansException if the bean could not be created + * @see BeanFactory#getBean(Class) */ - public static T qualifiedBeanOfType(BeanFactory beanFactory, Class beanType, String qualifier) { + public static T qualifiedBeanOfType(BeanFactory beanFactory, Class beanType, String qualifier) + throws BeansException { + + Assert.notNull(beanFactory, "BeanFactory must not be null"); + if (beanFactory instanceof ConfigurableListableBeanFactory) { // Full qualifier matching supported. return qualifiedBeanOfType((ConfigurableListableBeanFactory) beanFactory, beanType, qualifier); @@ -74,7 +84,6 @@ else if (beanFactory.containsBean(qualifier)) { * @param beanType the type of bean to retrieve * @param qualifier the qualifier for selecting between multiple bean matches * @return the matching bean of type {@code T} (never {@code null}) - * @throws NoSuchBeanDefinitionException if no matching bean of type {@code T} found */ private static T qualifiedBeanOfType(ConfigurableListableBeanFactory bf, Class beanType, String qualifier) { String[] candidateBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(bf, beanType); @@ -82,8 +91,7 @@ private static T qualifiedBeanOfType(ConfigurableListableBeanFactory bf, Cla for (String beanName : candidateBeans) { if (isQualifierMatch(qualifier, beanName, bf)) { if (matchingBean != null) { - throw new NoSuchBeanDefinitionException(qualifier, "No unique " + beanType.getSimpleName() + - " bean found for qualifier '" + qualifier + "'"); + throw new NoUniqueBeanDefinitionException(beanType, matchingBean, beanName); } matchingBean = beanName; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java index 374332395c73..2804302c22d1 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java @@ -40,6 +40,7 @@ import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; +import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; /** @@ -349,7 +350,7 @@ public LifecycleElement(Method method) { } this.method = method; this.identifier = (Modifier.isPrivate(method.getModifiers()) ? - method.getDeclaringClass() + "." + method.getName() : method.getName()); + ClassUtils.getQualifiedMethodName(method) : method.getName()); } public Method getMethod() { 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 af64331ce9c4..6f9caba818ff 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -80,9 +80,8 @@ public void inject(Object target, String beanName, PropertyValues pvs) throws Th Collection elementsToIterate = (this.checkedElements != null ? this.checkedElements : this.injectedElements); if (!elementsToIterate.isEmpty()) { - boolean debug = logger.isDebugEnabled(); for (InjectedElement element : elementsToIterate) { - if (debug) { + if (logger.isDebugEnabled()) { logger.debug("Processing injected element of bean '" + beanName + "': " + element); } element.inject(target, beanName, pvs); @@ -109,7 +108,10 @@ public static boolean needsRefresh(InjectionMetadata metadata, Class clazz) { } - public static abstract class InjectedElement { + /** + * A single injected element. + */ + public abstract static class InjectedElement { protected final Member member; @@ -216,6 +218,7 @@ else if (pvs instanceof MutablePropertyValues) { } /** + * Clear property skipping for this element. * @since 3.2.13 */ protected void clearPropertySkipping(PropertyValues pvs) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java index 0f6d10216ca8..8e6ecb3a5fc9 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.beans.factory.annotation; import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.LinkedHashSet; import java.util.Map; @@ -49,6 +50,7 @@ * * @author Mark Fisher * @author Juergen Hoeller + * @author Stephane Nicoll * @since 2.5 * @see AutowireCandidateQualifier * @see Qualifier @@ -225,8 +227,12 @@ protected boolean checkQualifier( qualifier = bd.getQualifier(ClassUtils.getShortName(type)); } if (qualifier == null) { - // First, check annotation on factory method, if applicable - Annotation targetAnnotation = getFactoryMethodAnnotation(bd, type); + // First, check annotation on qualified element, if any + Annotation targetAnnotation = getQualifiedElementAnnotation(bd, type); + // Then, check annotation on factory method, if applicable + if (targetAnnotation == null) { + targetAnnotation = getFactoryMethodAnnotation(bd, type); + } if (targetAnnotation == null) { RootBeanDefinition dbd = getResolvedDecoratedDefinition(bd); if (dbd != null) { @@ -291,6 +297,11 @@ protected boolean checkQualifier( return true; } + protected Annotation getQualifiedElementAnnotation(RootBeanDefinition bd, Class type) { + AnnotatedElement qualifiedElement = bd.getQualifiedElement(); + return (qualifiedElement != null ? AnnotationUtils.getAnnotation(qualifiedElement, type) : null); + } + protected Annotation getFactoryMethodAnnotation(RootBeanDefinition bd, Class type) { Method resolvedFactoryMethod = bd.getResolvedFactoryMethod(); return (resolvedFactoryMethod != null ? AnnotationUtils.getAnnotation(resolvedFactoryMethod, type) : null); @@ -298,7 +309,21 @@ protected Annotation getFactoryMethodAnnotation(RootBeanDefinition bd, Class 0) { // qualifier annotations have to be local + AnnotationAttributes attr = AnnotatedElementUtils.getMergedAnnotationAttributes( + AnnotatedElementUtils.forAnnotations(annotationsToSearch), this.valueAnnotationType); + if (attr != null) { + return extractValue(attr); + } } return null; } 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 ebc3cbf53c30..31c0352879a3 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-2013 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,8 +55,8 @@ * and obviates the need (in part) for a developer to code a method that * simply checks that all required properties have actually been set. * - *

Please note that an 'init' method may still need to implemented (and may - * still be desirable), because all that this class does is enforce that a + *

Please note that an 'init' method may still need to be implemented (and may + * still be desirable), because all that this class does is enforcing that a * 'required' property has actually been configured with a value. It does * not check anything else... In particular, it does not check that a * configured value is not {@code null}. @@ -141,8 +141,7 @@ public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, C @Override public PropertyValues postProcessPropertyValues( - PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) - throws BeansException { + PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { if (!this.validatedBeanNames.contains(beanName)) { if (!shouldSkip(this.beanFactory, beanName)) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java index 85faab006b05..ab473c3121bc 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBeanNotInitializedException; import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; @@ -153,7 +154,7 @@ public final T getObject() throws Exception { } /** - * Determine an 'eager singleton' instance, exposed in case of a + * Determine an 'early singleton' instance, exposed in case of a * circular reference. Not called in a non-circular scenario. */ @SuppressWarnings("unchecked") @@ -176,9 +177,7 @@ private T getEarlySingletonInstance() throws Exception { * @throws IllegalStateException if the singleton instance is not initialized */ private T getSingletonInstance() throws IllegalStateException { - if (!this.initialized) { - throw new IllegalStateException("Singleton instance not initialized yet"); - } + Assert.state(this.initialized, "Singleton instance not initialized yet"); return this.singletonInstance; } @@ -218,7 +217,7 @@ public void destroy() throws Exception { * FactoryBean is supposed to implement, for use with an 'early singleton * proxy' that will be exposed in case of a circular reference. *

The default implementation returns this FactoryBean's object type, - * provided that it is an interface, or {@code null} else. The latter + * provided that it is an interface, or {@code null} otherwise. The latter * indicates that early singleton access is not supported by this FactoryBean. * This will lead to a FactoryBeanNotInitializedException getting thrown. * @return the interfaces to use for 'early singletons', diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java index 43602b48ebf8..61b524077028 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java @@ -21,6 +21,8 @@ import org.springframework.beans.BeansException; import org.springframework.beans.TypeConverter; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; /** * Extension of the {@link org.springframework.beans.factory.BeanFactory} @@ -154,15 +156,6 @@ public interface AutowireCapableBeanFactory extends BeanFactory { */ Object configureBean(Object existingBean, String beanName) throws BeansException; - /** - * Resolve the specified dependency against the beans defined in this factory. - * @param descriptor the descriptor for the dependency - * @param beanName the name of the bean which declares the present dependency - * @return the resolved object, or {@code null} if none found - * @throws BeansException if dependency resolution failed - */ - Object resolveDependency(DependencyDescriptor descriptor, String beanName) throws BeansException; - //------------------------------------------------------------------------- // Specialized methods for fine-grained control over the bean lifecycle @@ -312,18 +305,55 @@ Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String be */ void destroyBean(Object existingBean); + + //------------------------------------------------------------------------- + // Delegate methods for resolving injection points + //------------------------------------------------------------------------- + + /** + * Resolve the bean instance that uniquely matches the given object type, if any, + * including its bean name. + *

This is effectively a variant of {@link #getBean(Class)} which preserves the + * bean name of the matching instance. + * @param requiredType type the bean must match; can be an interface or superclass. + * {@code null} is disallowed. + * @return the bean name plus bean instance + * @throws NoSuchBeanDefinitionException if no matching bean was found + * @throws NoUniqueBeanDefinitionException if more than one matching bean was found + * @throws BeansException if the bean could not be created + * @since 4.3.3 + * @see #getBean(Class) + */ + NamedBeanHolder resolveNamedBean(Class requiredType) throws BeansException; + + /** + * Resolve the specified dependency against the beans defined in this factory. + * @param descriptor the descriptor for the dependency (field/method/constructor) + * @param requestingBeanName the name of the bean which declares the given dependency + * @return the resolved object, or {@code null} if none found + * @throws NoSuchBeanDefinitionException if no matching bean was found + * @throws NoUniqueBeanDefinitionException if more than one matching bean was found + * @throws BeansException if dependency resolution failed for any other reason + * @since 2.5 + * @see #resolveDependency(DependencyDescriptor, String, Set, TypeConverter) + */ + Object resolveDependency(DependencyDescriptor descriptor, String requestingBeanName) throws BeansException; + /** * Resolve the specified dependency against the beans defined in this factory. - * @param descriptor the descriptor for the dependency - * @param beanName the name of the bean which declares the present dependency + * @param descriptor the descriptor for the dependency (field/method/constructor) + * @param requestingBeanName the name of the bean which declares the given dependency * @param autowiredBeanNames a Set that all names of autowired beans (used for - * resolving the present dependency) are supposed to be added to - * @param typeConverter the TypeConverter to use for populating arrays and - * collections + * resolving the given dependency) are supposed to be added to + * @param typeConverter the TypeConverter to use for populating arrays and collections * @return the resolved object, or {@code null} if none found - * @throws BeansException if dependency resolution failed + * @throws NoSuchBeanDefinitionException if no matching bean was found + * @throws NoUniqueBeanDefinitionException if more than one matching bean was found + * @throws BeansException if dependency resolution failed for any other reason + * @since 2.5 + * @see DependencyDescriptor */ - Object resolveDependency(DependencyDescriptor descriptor, String beanName, + Object resolveDependency(DependencyDescriptor descriptor, String requestingBeanName, Set autowiredBeanNames, TypeConverter typeConverter) throws BeansException; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java index 535d2a8e9611..5ba7068f41ed 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,10 +79,7 @@ public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { int ROLE_INFRASTRUCTURE = 2; - /** - * Return the name of the parent definition of this bean definition, if any. - */ - String getParentName(); + // Modifiable attributes /** * Set the name of the parent definition of this bean definition, if any. @@ -90,46 +87,40 @@ public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { void setParentName(String parentName); /** - * Return the current bean class name of this bean definition. - *

Note that this does not have to be the actual class name used at runtime, in - * case of a child definition overriding/inheriting the class name from its parent. - * Hence, do not consider this to be the definitive bean type at runtime but - * rather only use it for parsing purposes at the individual bean definition level. + * Return the name of the parent definition of this bean definition, if any. */ - String getBeanClassName(); + String getParentName(); /** - * Override the bean class name of this bean definition. + * Specify the bean class name of this bean definition. *

The class name can be modified during bean factory post-processing, * typically replacing the original class name with a parsed variant of it. + * @see #setParentName + * @see #setFactoryBeanName + * @see #setFactoryMethodName */ void setBeanClassName(String beanClassName); /** - * Return the factory bean name, if any. - */ - String getFactoryBeanName(); - - /** - * Specify the factory bean to use, if any. - */ - void setFactoryBeanName(String factoryBeanName); - - /** - * Return a factory method, if any. + * Return the current bean class name of this bean definition. + *

Note that this does not have to be the actual class name used at runtime, in + * case of a child definition overriding/inheriting the class name from its parent. + * Also, this may just be the class that a factory method is called on, or it may + * even be empty in case of a factory bean reference that a method is called on. + * Hence, do not consider this to be the definitive bean type at runtime but + * rather only use it for parsing purposes at the individual bean definition level. + * @see #getParentName() + * @see #getFactoryBeanName() + * @see #getFactoryMethodName() */ - String getFactoryMethodName(); + String getBeanClassName(); /** - * Specify a factory method, if any. This method will be invoked with - * constructor arguments, or with no arguments if none are specified. - * The method will be invoked on the specified factory bean, if any, - * or otherwise as a static method on the local bean class. - * @param factoryMethodName static factory method name, - * or {@code null} if normal constructor creation should be used - * @see #getBeanClassName() + * Override the target scope of this bean, specifying a new scope name. + * @see #SCOPE_SINGLETON + * @see #SCOPE_PROTOTYPE */ - void setFactoryMethodName(String factoryMethodName); + void setScope(String scope); /** * Return the name of the current target scope for this bean, @@ -138,11 +129,11 @@ public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { String getScope(); /** - * Override the target scope of this bean, specifying a new scope name. - * @see #SCOPE_SINGLETON - * @see #SCOPE_PROTOTYPE + * Set whether this bean should be lazily initialized. + *

If {@code false}, the bean will get instantiated on startup by bean + * factories that perform eager initialization of singletons. */ - void setScope(String scope); + void setLazyInit(boolean lazyInit); /** * Return whether this bean should be lazily initialized, i.e. not @@ -151,11 +142,10 @@ public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { boolean isLazyInit(); /** - * Set whether this bean should be lazily initialized. - *

If {@code false}, the bean will get instantiated on startup by bean - * factories that perform eager initialization of singletons. + * Set the names of the beans that this bean depends on being initialized. + * The bean factory will guarantee that these beans get initialized first. */ - void setLazyInit(boolean lazyInit); + void setDependsOn(String... dependsOn); /** * Return the bean names that this bean depends on. @@ -163,10 +153,13 @@ public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { String[] getDependsOn(); /** - * Set the names of the beans that this bean depends on being initialized. - * The bean factory will guarantee that these beans get initialized first. + * Set whether this bean is a candidate for getting autowired into some other bean. + *

Note that this flag is designed to only affect type-based autowiring. + * It does not affect explicit references by name, which will get resolved even + * if the specified bean is not marked as an autowire candidate. As a consequence, + * autowiring by name will nevertheless inject a bean if the name matches. */ - void setDependsOn(String... dependsOn); + void setAutowireCandidate(boolean autowireCandidate); /** * Return whether this bean is a candidate for getting autowired into some other bean. @@ -174,24 +167,43 @@ public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { boolean isAutowireCandidate(); /** - * Set whether this bean is a candidate for getting autowired into some other bean. + * Set whether this bean is a primary autowire candidate. + *

If this value is {@code true} for exactly one bean among multiple + * matching candidates, it will serve as a tie-breaker. */ - void setAutowireCandidate(boolean autowireCandidate); + void setPrimary(boolean primary); /** * Return whether this bean is a primary autowire candidate. - * If this value is true for exactly one bean among multiple - * matching candidates, it will serve as a tie-breaker. */ boolean isPrimary(); /** - * Set whether this bean is a primary autowire candidate. - *

If this value is true for exactly one bean among multiple - * matching candidates, it will serve as a tie-breaker. + * Specify the factory bean to use, if any. + * This the name of the bean to call the specified factory method on. + * @see #setFactoryMethodName */ - void setPrimary(boolean primary); + void setFactoryBeanName(String factoryBeanName); + + /** + * Return the factory bean name, if any. + */ + String getFactoryBeanName(); + + /** + * Specify a factory method, if any. This method will be invoked with + * constructor arguments, or with no arguments if none are specified. + * The method will be invoked on the specified factory bean, if any, + * or otherwise as a static method on the local bean class. + * @see #setFactoryBeanName + * @see #setBeanClassName + */ + void setFactoryMethodName(String factoryMethodName); + /** + * Return a factory method, if any. + */ + String getFactoryMethodName(); /** * Return the constructor argument values for this bean. @@ -208,6 +220,8 @@ public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { MutablePropertyValues getPropertyValues(); + // Read-only attributes + /** * Return whether this a Singleton, with a single, shared instance * returned on all calls. @@ -218,6 +232,7 @@ public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { /** * Return whether this a Prototype, with an independent instance * returned for each call. + * @since 3.0 * @see #SCOPE_PROTOTYPE */ boolean isPrototype(); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/CustomScopeConfigurer.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/CustomScopeConfigurer.java index dc1516ab026b..a110bc4461bb 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/CustomScopeConfigurer.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/CustomScopeConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -102,12 +102,12 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) } else if (value instanceof Class) { Class scopeClass = (Class) value; - Assert.isAssignable(Scope.class, scopeClass); + Assert.isAssignable(Scope.class, scopeClass, "Invalid scope class"); beanFactory.registerScope(scopeKey, (Scope) BeanUtils.instantiateClass(scopeClass)); } else if (value instanceof String) { Class scopeClass = ClassUtils.resolveClassName((String) value, this.beanClassLoader); - Assert.isAssignable(Scope.class, scopeClass); + Assert.isAssignable(Scope.class, scopeClass, "Invalid scope class"); beanFactory.registerScope(scopeKey, (Scope) BeanUtils.instantiateClass(scopeClass)); } else { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java index 82c6883cc189..bda5e0c0b7dc 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,6 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.InjectionPoint; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; -import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.GenericTypeResolver; import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; @@ -63,6 +62,8 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable private Class containingClass; + private volatile ResolvableType resolvableType; + /** * Create a new descriptor for a method or constructor parameter. @@ -214,6 +215,7 @@ public Object resolveCandidate(String beanName, Class requiredType, BeanFacto */ public void increaseNestingLevel() { this.nestingLevel++; + this.resolvableType = null; if (this.methodParameter != null) { this.methodParameter.increaseNestingLevel(); } @@ -227,6 +229,7 @@ public void increaseNestingLevel() { */ public void setContainingClass(Class containingClass) { this.containingClass = containingClass; + this.resolvableType = null; if (this.methodParameter != null) { GenericTypeResolver.resolveParameterType(this.methodParameter, containingClass); } @@ -237,14 +240,20 @@ public void setContainingClass(Class containingClass) { * @since 4.0 */ public ResolvableType getResolvableType() { - return (this.field != null ? ResolvableType.forField(this.field, this.nestingLevel, this.containingClass) : - ResolvableType.forMethodParameter(this.methodParameter)); + ResolvableType resolvableType = this.resolvableType; + if (resolvableType == null) { + resolvableType = (this.field != null ? + ResolvableType.forField(this.field, this.nestingLevel, this.containingClass) : + ResolvableType.forMethodParameter(this.methodParameter)); + this.resolvableType = resolvableType; + } + return resolvableType; } /** * Return whether a fallback match is allowed. *

This is {@code false} by default but may be overridden to return {@code true} in order - * to suggest to a {@link org.springframework.beans.factory.support.AutowireCandidateResolver} + * to suggest to an {@link org.springframework.beans.factory.support.AutowireCandidateResolver} * that a fallback match is acceptable as well. * @since 4.0 */ @@ -299,7 +308,6 @@ public Class getDependencyType() { Type[] args = ((ParameterizedType) type).getActualTypeArguments(); type = args[args.length - 1]; } - // TODO: Object.class if unresolvable } if (type instanceof Class) { return (Class) type; @@ -324,31 +332,37 @@ else if (type instanceof ParameterizedType) { /** * Determine the generic element type of the wrapped Collection parameter/field, if any. * @return the generic type, or {@code null} if none + * @deprecated as of 4.3.6, in favor of direct {@link ResolvableType} usage */ + @Deprecated public Class getCollectionType() { return (this.field != null ? - GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.nestingLevel) : - GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter)); + org.springframework.core.GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.nestingLevel) : + org.springframework.core.GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter)); } /** * Determine the generic key type of the wrapped Map parameter/field, if any. * @return the generic type, or {@code null} if none + * @deprecated as of 4.3.6, in favor of direct {@link ResolvableType} usage */ + @Deprecated public Class getMapKeyType() { return (this.field != null ? - GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.nestingLevel) : - GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter)); + org.springframework.core.GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.nestingLevel) : + org.springframework.core.GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter)); } /** * Determine the generic value type of the wrapped Map parameter/field, if any. * @return the generic type, or {@code null} if none + * @deprecated as of 4.3.6, in favor of direct {@link ResolvableType} usage */ + @Deprecated public Class getMapValueType() { return (this.field != null ? - GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.nestingLevel) : - GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter)); + org.springframework.core.GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.nestingLevel) : + org.springframework.core.GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter)); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/DestructionAwareBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/DestructionAwareBeanPostProcessor.java index 92316f13b297..e2e8c4617254 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/DestructionAwareBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/DestructionAwareBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,16 +30,16 @@ public interface DestructionAwareBeanPostProcessor extends BeanPostProcessor { /** - * Apply this BeanPostProcessor to the given bean instance before - * its destruction. Can invoke custom destruction callbacks. - *

Like DisposableBean's {@code destroy} and a custom destroy method, - * this callback just applies to singleton beans in the factory (including - * inner beans). + * Apply this BeanPostProcessor to the given bean instance before its + * destruction, e.g. invoking custom destruction callbacks. + *

Like DisposableBean's {@code destroy} and a custom destroy method, this + * callback will only apply to beans which the container fully manages the + * lifecycle for. This is usually the case for singletons and scoped beans. * @param bean the bean instance to be destroyed * @param beanName the name of the bean * @throws org.springframework.beans.BeansException in case of errors - * @see org.springframework.beans.factory.DisposableBean - * @see org.springframework.beans.factory.support.AbstractBeanDefinition#setDestroyMethodName + * @see org.springframework.beans.factory.DisposableBean#destroy() + * @see org.springframework.beans.factory.support.AbstractBeanDefinition#setDestroyMethodName(String) */ void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException; 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 964ee204b276..62c0dbdfaa53 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-2012 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,9 +71,8 @@ public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor { /** * Perform operations after the bean has been instantiated, via a constructor or factory method, * but before Spring property population (from explicit properties or autowiring) occurs. - *

This is the ideal callback for performing field injection on the given bean instance. - * See Spring's own {@link org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor} - * for a typical example. + *

This is the ideal callback for performing custom field injection on the given bean + * instance, right before Spring's autowiring kicks in. * @param bean the bean instance created, with properties not having been set yet * @param beanName the name of the bean * @return {@code true} if properties should be set on the bean; {@code false} @@ -103,7 +102,6 @@ public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor { * @see org.springframework.beans.MutablePropertyValues */ PropertyValues postProcessPropertyValues( - PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) - throws BeansException; + PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessorAdapter.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessorAdapter.java index 3e02846fcb5b..3f7e1746b70d 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessorAdapter.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessorAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -66,8 +66,7 @@ public boolean postProcessAfterInstantiation(Object bean, String beanName) throw @Override public PropertyValues postProcessPropertyValues( - PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) - throws BeansException { + PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { return pvs; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/ListFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/ListFactoryBean.java index fb251c4243b3..3ab96831092d 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/ListFactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/ListFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import org.springframework.beans.BeanUtils; import org.springframework.beans.TypeConverter; -import org.springframework.core.GenericCollectionTypeResolver; +import org.springframework.core.ResolvableType; /** * Simple factory for shared List instances. Allows for central setup @@ -86,7 +86,7 @@ protected List createInstance() { } Class valueType = null; if (this.targetListClass != null) { - valueType = GenericCollectionTypeResolver.getCollectionType(this.targetListClass); + valueType = ResolvableType.forClass(this.targetListClass).asCollection().resolveGeneric(); } if (valueType != null) { TypeConverter converter = getBeanTypeConverter(); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/MapFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/MapFactoryBean.java index f1ba9cfd4518..2e9ca7378bdc 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/MapFactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/MapFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import org.springframework.beans.BeanUtils; import org.springframework.beans.TypeConverter; -import org.springframework.core.GenericCollectionTypeResolver; +import org.springframework.core.ResolvableType; /** * Simple factory for shared Map instances. Allows for central setup @@ -87,8 +87,9 @@ protected Map createInstance() { Class keyType = null; Class valueType = null; if (this.targetMapClass != null) { - keyType = GenericCollectionTypeResolver.getMapKeyType(this.targetMapClass); - valueType = GenericCollectionTypeResolver.getMapValueType(this.targetMapClass); + ResolvableType mapType = ResolvableType.forClass(this.targetMapClass).asMap(); + keyType = mapType.resolveGeneric(0); + valueType = mapType.resolveGeneric(1); } if (keyType != null || valueType != null) { TypeConverter converter = getBeanTypeConverter(); 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 fe3f25b47b21..1c3c3d948944 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -69,7 +69,7 @@ * </bean> * * <bean id="javaVersion" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> - * <property name="targetObject" value="sysProps"/> + * <property name="targetObject" ref="sysProps"/> * <property name="targetMethod" value="getProperty"/> * <property name="arguments" value="java.version"/> * </bean> diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/NamedBeanHolder.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/NamedBeanHolder.java new file mode 100644 index 000000000000..04e5e39a372f --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/NamedBeanHolder.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.beans.factory.config; + +import org.springframework.beans.factory.NamedBean; +import org.springframework.util.Assert; + +/** + * A simple holder for a given bean name plus bean instance. + * + * @author Juergen Hoeller + * @since 4.3.3 + * @see AutowireCapableBeanFactory#resolveNamedBean(Class) + */ +public class NamedBeanHolder implements NamedBean { + + private final String beanName; + + private final T beanInstance; + + + /** + * Create a new holder for the given bean name plus instance. + * @param beanName the name of the bean + * @param beanInstance the corresponding bean instance + */ + public NamedBeanHolder(String beanName, T beanInstance) { + Assert.notNull(beanName, "Bean name must not be null"); + this.beanName = beanName; + this.beanInstance = beanInstance; + } + + + /** + * Return the name of the bean (never {@code null}). + */ + @Override + public String getBeanName() { + return this.beanName; + } + + /** + * Return the corresponding bean instance (can be {@code null}). + */ + public T getBeanInstance() { + return this.beanInstance; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/Scope.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/Scope.java index 2911a123a1b1..4e313c7919c1 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/Scope.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/Scope.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,6 +68,7 @@ public interface Scope { * @param objectFactory the {@link ObjectFactory} to use to create the scoped * object if it is not present in the underlying storage mechanism * @return the desired object (never {@code null}) + * @throws IllegalStateException if the underlying scope is not currently active */ Object get(String name, ObjectFactory objectFactory); @@ -84,6 +85,7 @@ public interface Scope { * removing an object. * @param name the name of the object to remove * @return the removed object, or {@code null} if no object was present + * @throws IllegalStateException if the underlying scope is not currently active * @see #registerDestructionCallback */ Object remove(String name); @@ -112,6 +114,7 @@ public interface Scope { * so it can safely be executed without an enclosing try-catch block. * Furthermore, the Runnable will usually be serializable, provided * that its target object is serializable as well. + * @throws IllegalStateException if the underlying scope is not currently active * @see org.springframework.beans.factory.DisposableBean * @see org.springframework.beans.factory.support.AbstractBeanDefinition#getDestroyMethodName() * @see DestructionAwareBeanPostProcessor @@ -123,6 +126,7 @@ public interface Scope { * E.g. the HttpServletRequest object for key "request". * @param key the contextual key * @return the corresponding object, or {@code null} if none found + * @throws IllegalStateException if the underlying scope is not currently active */ Object resolveContextualObject(String key); @@ -139,6 +143,7 @@ public interface Scope { * underlying storage mechanism has no obvious candidate for such an ID. * @return the conversation ID, or {@code null} if there is no * conversation ID for the current scope + * @throws IllegalStateException if the underlying scope is not currently active */ String getConversationId(); 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 92345d7eab92..729abc7cc566 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -281,15 +281,15 @@ public void afterPropertiesSet() { @SuppressWarnings("unchecked") protected Constructor determineServiceLocatorExceptionConstructor(Class exceptionClass) { try { - return (Constructor) exceptionClass.getConstructor(new Class[] {String.class, Throwable.class}); + return (Constructor) exceptionClass.getConstructor(String.class, Throwable.class); } catch (NoSuchMethodException ex) { try { - return (Constructor) exceptionClass.getConstructor(new Class[] {Throwable.class}); + return (Constructor) exceptionClass.getConstructor(Throwable.class); } catch (NoSuchMethodException ex2) { try { - return (Constructor) exceptionClass.getConstructor(new Class[] {String.class}); + return (Constructor) exceptionClass.getConstructor(String.class); } catch (NoSuchMethodException ex3) { throw new IllegalArgumentException( @@ -357,7 +357,7 @@ else if (ReflectionUtils.isHashCodeMethod(method)) { return System.identityHashCode(proxy); } else if (ReflectionUtils.isToStringMethod(method)) { - return "Service locator: " + serviceLocatorInterface.getName(); + return "Service locator: " + serviceLocatorInterface; } else { return invokeServiceLocatorMethod(method, args); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/SetFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/SetFactoryBean.java index c6b573d3dbeb..cd05acd23822 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/SetFactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/SetFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import org.springframework.beans.BeanUtils; import org.springframework.beans.TypeConverter; -import org.springframework.core.GenericCollectionTypeResolver; +import org.springframework.core.ResolvableType; /** * Simple factory for shared Set instances. Allows for central setup @@ -86,7 +86,7 @@ protected Set createInstance() { } Class valueType = null; if (this.targetSetClass != null) { - valueType = GenericCollectionTypeResolver.getCollectionType(this.targetSetClass); + valueType = ResolvableType.forClass(this.targetSetClass).asCollection().resolveGeneric(); } if (valueType != null) { TypeConverter converter = getBeanTypeConverter(); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlMapFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlMapFactoryBean.java index 7428f997f934..755819a10a07 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlMapFactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlMapFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,12 +25,16 @@ import org.springframework.beans.factory.InitializingBean; /** - * Factory for a Map that reads from a YAML source. YAML is a nice human-readable - * format for configuration, and it has some useful hierarchical properties. It's - * more or less a superset of JSON, so it has a lot of similar features. If - * multiple resources are provided the later ones will override entries in the - * earlier ones hierarchically - that is all entries with the same nested key of - * type Map at any depth are merged. For example: + * Factory for a {@code Map} that reads from a YAML source, preserving the + * YAML-declared value types and their structure. + * + *

YAML is a nice human-readable format for configuration, and it has some + * useful hierarchical properties. It's more or less a superset of JSON, so it + * has a lot of similar features. + * + *

If multiple resources are provided the later ones will override entries in + * the earlier ones hierarchically; that is, all entries with the same nested key + * of type {@code Map} at any depth are merged. For example: * *

  * foo:
@@ -62,6 +66,7 @@
  * with the value in the second, but its nested values are merged.
  *
  * @author Dave Syer
+ * @author Juergen Hoeller
  * @since 4.1
  */
 public class YamlMapFactoryBean extends YamlProcessor implements FactoryBean>, InitializingBean {
@@ -104,10 +109,10 @@ public Class getObjectType() {
 
 	/**
 	 * Template method that subclasses may override to construct the object
-	 * returned by this factory. The default implementation returns the
-	 * merged Map instance.
+	 * returned by this factory.
 	 * 

Invoked lazily the first time {@link #getObject()} is invoked in * case of a shared singleton; else, on each {@link #getObject()} call. + *

The default implementation returns the merged {@code Map} instance. * @return the object returned by this factory * @see #process(java.util.Map, MatchCallback) */ 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 ac01eb74b7ab..2f43f2e84cf2 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-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Properties; import java.util.Set; @@ -37,6 +36,7 @@ import org.yaml.snakeyaml.parser.ParserException; import org.yaml.snakeyaml.reader.UnicodeReader; +import org.springframework.core.CollectionFactory; import org.springframework.core.io.Resource; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -45,6 +45,7 @@ * Base class for YAML factories. * * @author Dave Syer + * @author Juergen Hoeller * @since 4.1 */ public abstract class YamlProcessor { @@ -75,15 +76,16 @@ public abstract class YamlProcessor { * name: My Cool App *

* when mapped with - * documentMatchers = YamlProcessor.mapMatcher({"environment": "prod"}) + *
+	 * setDocumentMatchers(properties ->
+	 *     ("prod".equals(properties.getProperty("environment")) ? MatchStatus.FOUND : MatchStatus.NOT_FOUND));
+	 * 
* would end up as *
 	 * environment=prod
 	 * url=http://foo.bar.com
 	 * name=My Cool App
-	 * url=http://dev.bar.com
 	 * 
- * @param matchers a map of keys to value patterns (regular expressions) */ public void setDocumentMatchers(DocumentMatcher... matchers) { this.documentMatchers = Arrays.asList(matchers); @@ -92,8 +94,7 @@ public void setDocumentMatchers(DocumentMatcher... matchers) { /** * Flag indicating that a document for which all the * {@link #setDocumentMatchers(DocumentMatcher...) document matchers} abstain will - * nevertheless match. - * @param matchDefault the flag to set (default true) + * nevertheless match. Default is {@code true}. */ public void setMatchDefault(boolean matchDefault) { this.matchDefault = matchDefault; @@ -102,9 +103,7 @@ public void setMatchDefault(boolean matchDefault) { /** * Method to use for resolving resources. Each resource will be converted to a Map, * so this property is used to decide which map entries to keep in the final output - * from this factory. - * @param resolutionMethod the resolution method to set (defaults to - * {@link ResolutionMethod#OVERRIDE}). + * from this factory. Default is {@link ResolutionMethod#OVERRIDE}. */ public void setResolutionMethod(ResolutionMethod resolutionMethod) { Assert.notNull(resolutionMethod, "ResolutionMethod must not be null"); @@ -199,7 +198,7 @@ private Map asMap(Object object) { } Map map = (Map) object; - for (Entry entry : map.entrySet()) { + for (Map.Entry entry : map.entrySet()) { Object value = entry.getValue(); if (value instanceof Map) { value = asMap(value); @@ -217,7 +216,7 @@ private Map asMap(Object object) { } private boolean process(Map map, MatchCallback callback) { - Properties properties = new Properties(); + Properties properties = CollectionFactory.createStringAdaptingProperties(); properties.putAll(getFlattenedMap(map)); if (this.documentMatchers.isEmpty()) { @@ -271,14 +270,14 @@ protected final Map getFlattenedMap(Map source) } private void buildFlattenedMap(Map result, Map source, String path) { - for (Entry entry : source.entrySet()) { + for (Map.Entry entry : source.entrySet()) { String key = entry.getKey(); if (StringUtils.hasText(path)) { if (key.startsWith("[")) { key = path + key; } else { - key = path + "." + key; + key = path + '.' + key; } } Object value = entry.getValue(); @@ -302,21 +301,23 @@ else if (value instanceof Collection) { } } else { - result.put(key, value != null ? value : ""); + result.put(key, (value != null ? value : "")); } } } /** - * Callback interface used to process properties in a resulting map. + * Callback interface used to process the YAML parsing results. */ public interface MatchCallback { /** - * Process the properties. - * @param properties the properties to process - * @param map a mutable result map + * Process the given representation of the parsing results. + * @param properties the properties to process (as a flattened + * representation with indexed keys in case of a collection or map) + * @param map the result map (preserving the original value structure + * in the YAML document) */ void process(Properties properties, Map map); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBean.java index 0374ddbf5509..951ef3183b79 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,13 +21,23 @@ import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.CollectionFactory; /** - * Factory for Java Properties that reads from a YAML source. YAML is a nice - * human-readable format for configuration, and it has some useful hierarchical - * properties. It's more or less a superset of JSON, so it has a lot of similar - * features. The Properties created by this factory have nested paths for - * hierarchical objects, so for instance this YAML + * Factory for {@link java.util.Properties} that reads from a YAML source, + * exposing a flat structure of String property values. + * + *

YAML is a nice human-readable format for configuration, and it has some + * useful hierarchical properties. It's more or less a superset of JSON, so it + * has a lot of similar features. + * + *

Note: All exposed values are of type {@code String} for access through + * the common {@link Properties#getProperty} method (e.g. in configuration property + * resolution through {@link PropertyResourceConfigurer#setProperties(Properties)}). + * If this is not desirable, use {@link YamlMapFactoryBean} instead. + * + *

The Properties created by this factory have nested paths for hierarchical + * objects, so for instance this YAML * *

  * environments:
@@ -39,7 +49,7 @@
  *     name: My Cool App
  * 
* - * is transformed into these Properties: + * is transformed into these properties: * *
  * environments.dev.url=http://dev.bar.com
@@ -57,7 +67,7 @@
  * - foo.bar.com
  * 
* - * becomes Java Properties like this: + * becomes properties like this: * *
  * servers[0]=dev.bar.com
@@ -66,6 +76,7 @@
  *
  * @author Dave Syer
  * @author Stephane Nicoll
+ * @author Juergen Hoeller
  * @since 4.1
  */
 public class YamlPropertiesFactoryBean extends YamlProcessor implements FactoryBean, InitializingBean {
@@ -116,7 +127,7 @@ public Class getObjectType() {
 	 * @see #process(MatchCallback) ()
 	 */
 	protected Properties createProperties() {
-		final Properties result = new Properties();
+		final Properties result = CollectionFactory.createStringAdaptingProperties();
 		process(new MatchCallback() {
 			@Override
 			public void process(Properties properties, Map map) {
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/BeanComponentDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/BeanComponentDefinition.java
index 2b88d827657d..e4b3b45b129b 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/BeanComponentDefinition.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/BeanComponentDefinition.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2017 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -76,8 +76,7 @@ private void findInnerBeanDefinitionsAndBeanReferences(BeanDefinition beanDefini
 		List innerBeans = new ArrayList();
 		List references = new ArrayList();
 		PropertyValues propertyValues = beanDefinition.getPropertyValues();
-		for (int i = 0; i < propertyValues.getPropertyValues().length; i++) {
-			PropertyValue propertyValue = propertyValues.getPropertyValues()[i];
+		for (PropertyValue propertyValue : propertyValues.getPropertyValues()) {
 			Object value = propertyValue.getValue();
 			if (value instanceof BeanDefinitionHolder) {
 				innerBeans.add(((BeanDefinitionHolder) value).getBeanDefinition());
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ComponentDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ComponentDefinition.java
index 49c5f9f7b578..2a32e6750f41 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ComponentDefinition.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ComponentDefinition.java
@@ -52,7 +52,7 @@
  * all {@link BeanReference BeanReferences} that are required to validate the configuration of the
  * overall logical entity as well as those required to provide full user visualisation of the configuration.
  * It is expected that certain {@link BeanReference BeanReferences} will not be important to
- * validation or to the user view of the configuration and as such these may be ommitted. A tool may wish to
+ * validation or to the user view of the configuration and as such these may be omitted. A tool may wish to
  * display any additional {@link BeanReference BeanReferences} sourced through the supplied
  * {@link BeanDefinition BeanDefinitions} but this is not considered to be a typical case.
  *
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/Problem.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/Problem.java
index 9f78a2909d4c..c317d5f9358d 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/Problem.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/Problem.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2017 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -42,7 +42,7 @@ public class Problem {
 
 	/**
 	 * Create a new instance of the {@link Problem} class.
-	 * @param message	a message detailing the problem
+	 * @param message a message detailing the problem
 	 * @param location the location within a bean configuration source that triggered the error
 	 */
 	public Problem(String message, Location location) {
@@ -51,7 +51,7 @@ public Problem(String message, Location location) {
 
 	/**
 	 * Create a new instance of the {@link Problem} class.
-	 * @param message	a message detailing the problem
+	 * @param message a message detailing the problem
 	 * @param parseState the {@link ParseState} at the time of the error
 	 * @param location the location within a bean configuration source that triggered the error
 	 */
@@ -61,8 +61,8 @@ public Problem(String message, Location location, ParseState parseState) {
 
 	/**
 	 * Create a new instance of the {@link Problem} class.
-	 * @param message    a message detailing the problem
-	 * @param rootCause the underlying expection that caused the error (may be {@code null})
+	 * @param message a message detailing the problem
+	 * @param rootCause the underlying exception that caused the error (may be {@code null})
 	 * @param parseState the {@link ParseState} at the time of the error
 	 * @param location the location within a bean configuration source that triggered the error
 	 */
@@ -107,7 +107,7 @@ public ParseState getParseState() {
 	}
 
 	/**
-	 * Get the underlying expection that caused the error (may be {@code null}).
+	 * Get the underlying exception that caused the error (may be {@code null}).
 	 */
 	public Throwable getRootCause() {
 		return this.rootCause;
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ReaderContext.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ReaderContext.java
index 64689f6486ce..95f81d8a9fc3 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ReaderContext.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ReaderContext.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2007 the original author or authors.
+ * Copyright 2002-2017 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -37,6 +37,13 @@ public class ReaderContext {
 	private final SourceExtractor sourceExtractor;
 
 
+	/**
+	 * Construct a new {@code ReaderContext}.
+	 * @param resource the XML bean definition resource
+	 * @param problemReporter the problem reporter in use
+	 * @param eventListener the event listener in use
+	 * @param sourceExtractor the source extractor in use
+	 */
 	public ReaderContext(Resource resource, ProblemReporter problemReporter,
 			ReaderEventListener eventListener, SourceExtractor sourceExtractor) {
 
@@ -51,83 +58,150 @@ public final Resource getResource() {
 	}
 
 
+	// Errors and warnings
+
+	/**
+	 * Raise a fatal error.
+	 */
 	public void fatal(String message, Object source) {
 		fatal(message, source, null, null);
 	}
 
+	/**
+	 * Raise a fatal error.
+	 */
 	public void fatal(String message, Object source, Throwable ex) {
 		fatal(message, source, null, ex);
 	}
 
+	/**
+	 * Raise a fatal error.
+	 */
 	public void fatal(String message, Object source, ParseState parseState) {
 		fatal(message, source, parseState, null);
 	}
 
+	/**
+	 * Raise a fatal error.
+	 */
 	public void fatal(String message, Object source, ParseState parseState, Throwable cause) {
 		Location location = new Location(getResource(), source);
 		this.problemReporter.fatal(new Problem(message, location, parseState, cause));
 	}
 
+	/**
+	 * Raise a regular error.
+	 */
 	public void error(String message, Object source) {
 		error(message, source, null, null);
 	}
 
+	/**
+	 * Raise a regular error.
+	 */
 	public void error(String message, Object source, Throwable ex) {
 		error(message, source, null, ex);
 	}
 
+	/**
+	 * Raise a regular error.
+	 */
 	public void error(String message, Object source, ParseState parseState) {
 		error(message, source, parseState, null);
 	}
 
+	/**
+	 * Raise a regular error.
+	 */
 	public void error(String message, Object source, ParseState parseState, Throwable cause) {
 		Location location = new Location(getResource(), source);
 		this.problemReporter.error(new Problem(message, location, parseState, cause));
 	}
 
+	/**
+	 * Raise a non-critical warning.
+	 */
 	public void warning(String message, Object source) {
 		warning(message, source, null, null);
 	}
 
+	/**
+	 * Raise a non-critical warning.
+	 */
 	public void warning(String message, Object source, Throwable ex) {
 		warning(message, source, null, ex);
 	}
 
+	/**
+	 * Raise a non-critical warning.
+	 */
 	public void warning(String message, Object source, ParseState parseState) {
 		warning(message, source, parseState, null);
 	}
 
+	/**
+	 * Raise a non-critical warning.
+	 */
 	public void warning(String message, Object source, ParseState parseState, Throwable cause) {
 		Location location = new Location(getResource(), source);
 		this.problemReporter.warning(new Problem(message, location, parseState, cause));
 	}
 
 
+	// Explicit parse events
+
+	/**
+	 * Fire an defaults-registered event.
+	 */
 	public void fireDefaultsRegistered(DefaultsDefinition defaultsDefinition) {
 		this.eventListener.defaultsRegistered(defaultsDefinition);
 	}
 
+	/**
+	 * Fire an component-registered event.
+	 */
 	public void fireComponentRegistered(ComponentDefinition componentDefinition) {
 		this.eventListener.componentRegistered(componentDefinition);
 	}
 
+	/**
+	 * Fire an alias-registered event.
+	 */
 	public void fireAliasRegistered(String beanName, String alias, Object source) {
 		this.eventListener.aliasRegistered(new AliasDefinition(beanName, alias, source));
 	}
 
+	/**
+	 * Fire an import-processed event.
+	 */
 	public void fireImportProcessed(String importedResource, Object source) {
 		this.eventListener.importProcessed(new ImportDefinition(importedResource, source));
 	}
 
+	/**
+	 * Fire an import-processed event.
+	 */
 	public void fireImportProcessed(String importedResource, Resource[] actualResources, Object source) {
 		this.eventListener.importProcessed(new ImportDefinition(importedResource, actualResources, source));
 	}
 
 
+	// Source extraction
+
+	/**
+	 * Return the source extractor in use.
+	 */
 	public SourceExtractor getSourceExtractor() {
 		return this.sourceExtractor;
 	}
 
+	/**
+	 * Call the source extractor for the given source object.
+	 * @param sourceCandidate the original source object
+	 * @return the source object to store, or {@code null} for none.
+	 * @see #getSourceExtractor()
+	 * @see SourceExtractor#extractSource
+	 */
 	public Object extractSource(Object sourceCandidate) {
 		return this.sourceExtractor.extractSource(sourceCandidate, this.resource);
 	}
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 cf25cd5b9a3e..55d89dc83c71 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-2015 the original author or authors.
+ * Copyright 2002-2018 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -31,9 +31,7 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
-import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
@@ -74,6 +72,7 @@
 import org.springframework.core.MethodParameter;
 import org.springframework.core.ParameterNameDiscoverer;
 import org.springframework.core.PriorityOrdered;
+import org.springframework.core.ResolvableType;
 import org.springframework.util.ClassUtils;
 import org.springframework.util.ObjectUtils;
 import org.springframework.util.ReflectionUtils;
@@ -145,7 +144,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
 	private final Set> ignoredDependencyInterfaces = new HashSet>();
 
 	/** Cache of unfinished FactoryBean instances: FactoryBean name --> BeanWrapper */
-	private final Map factoryBeanInstanceCache =
+	private final ConcurrentMap factoryBeanInstanceCache =
 			new ConcurrentHashMap(16);
 
 	/** Cache of filtered PropertyDescriptors: bean Class -> PropertyDescriptor array */
@@ -325,8 +324,8 @@ public Object configureBean(Object existingBean, String beanName) throws BeansEx
 	}
 
 	@Override
-	public Object resolveDependency(DependencyDescriptor descriptor, String beanName) throws BeansException {
-		return resolveDependency(descriptor, beanName, null, null);
+	public Object resolveDependency(DependencyDescriptor descriptor, String requestingBeanName) throws BeansException {
+		return resolveDependency(descriptor, requestingBeanName, null, null);
 	}
 
 
@@ -404,8 +403,8 @@ public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, S
 			throws BeansException {
 
 		Object result = existingBean;
-		for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
-			result = beanProcessor.postProcessBeforeInitialization(result, beanName);
+		for (BeanPostProcessor processor : getBeanPostProcessors()) {
+			result = processor.postProcessBeforeInitialization(result, beanName);
 			if (result == null) {
 				return result;
 			}
@@ -418,8 +417,8 @@ public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, St
 			throws BeansException {
 
 		Object result = existingBean;
-		for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
-			result = beanProcessor.postProcessAfterInitialization(result, beanName);
+		for (BeanPostProcessor processor : getBeanPostProcessors()) {
+			result = processor.postProcessAfterInitialization(result, beanName);
 			if (result == null) {
 				return result;
 			}
@@ -500,7 +499,9 @@ protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] ar
 	 * @see #instantiateUsingFactoryMethod
 	 * @see #autowireConstructor
 	 */
-	protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
+	protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
+			throws BeanCreationException {
+
 		// Instantiate the bean.
 		BeanWrapper instanceWrapper = null;
 		if (mbd.isSingleton()) {
@@ -511,11 +512,18 @@ protected Object doCreateBean(final String beanName, final RootBeanDefinition mb
 		}
 		final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
 		Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
+		mbd.resolvedTargetType = beanType;
 
 		// Allow post-processors to modify the merged bean definition.
 		synchronized (mbd.postProcessingLock) {
 			if (!mbd.postProcessed) {
-				applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
+				try {
+					applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
+				}
+				catch (Throwable ex) {
+					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
+							"Post-processing of merged bean definition failed", ex);
+				}
 				mbd.postProcessed = true;
 			}
 		}
@@ -550,7 +558,8 @@ public Object getObject() throws BeansException {
 				throw (BeanCreationException) ex;
 			}
 			else {
-				throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
+				throw new BeanCreationException(
+						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
 			}
 		}
 
@@ -586,7 +595,8 @@ else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
 			registerDisposableBeanIfNecessary(beanName, bean, mbd);
 		}
 		catch (BeanDefinitionValidationException ex) {
-			throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
+			throw new BeanCreationException(
+					mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
 		}
 
 		return exposedObject;
@@ -624,10 +634,11 @@ protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Clas
 	protected Class determineTargetType(String beanName, RootBeanDefinition mbd, Class... typesToMatch) {
 		Class targetType = mbd.getTargetType();
 		if (targetType == null) {
-			targetType = (mbd.getFactoryMethodName() != null ? getTypeForFactoryMethod(beanName, mbd, typesToMatch) :
+			targetType = (mbd.getFactoryMethodName() != null ?
+					getTypeForFactoryMethod(beanName, mbd, typesToMatch) :
 					resolveBeanClass(mbd, beanName, typesToMatch));
 			if (ObjectUtils.isEmpty(typesToMatch) || getTempClassLoader() == null) {
-				mbd.setTargetType(targetType);
+				mbd.resolvedTargetType = targetType;
 			}
 		}
 		return targetType;
@@ -648,9 +659,9 @@ protected Class determineTargetType(String beanName, RootBeanDefinition mbd,
 	 * @see #createBean
 	 */
 	protected Class getTypeForFactoryMethod(String beanName, RootBeanDefinition mbd, Class... typesToMatch) {
-		Class preResolved = mbd.resolvedFactoryMethodReturnType;
-		if (preResolved != null) {
-			return preResolved;
+		ResolvableType cachedReturnType = mbd.factoryMethodReturnType;
+		if (cachedReturnType != null) {
+			return cachedReturnType.resolve();
 		}
 
 		Class factoryClass;
@@ -674,26 +685,26 @@ protected Class getTypeForFactoryMethod(String beanName, RootBeanDefinition m
 		if (factoryClass == null) {
 			return null;
 		}
+		factoryClass = ClassUtils.getUserClass(factoryClass);
 
 		// If all factory methods have the same return type, return that type.
 		// Can't clearly figure out exact method due to type converting / autowiring!
 		Class commonType = null;
-		boolean cache = false;
+		Method uniqueCandidate = null;
 		int minNrOfArgs = mbd.getConstructorArgumentValues().getArgumentCount();
 		Method[] candidates = ReflectionUtils.getUniqueDeclaredMethods(factoryClass);
-		for (Method factoryMethod : candidates) {
-			if (Modifier.isStatic(factoryMethod.getModifiers()) == isStatic &&
-					factoryMethod.getName().equals(mbd.getFactoryMethodName()) &&
-					factoryMethod.getParameterTypes().length >= minNrOfArgs) {
+		for (Method candidate : candidates) {
+			if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate) &&
+					candidate.getParameterTypes().length >= minNrOfArgs) {
 				// Declared type variables to inspect?
-				if (factoryMethod.getTypeParameters().length > 0) {
+				if (candidate.getTypeParameters().length > 0) {
 					try {
 						// Fully resolve parameter names and argument values.
-						Class[] paramTypes = factoryMethod.getParameterTypes();
+						Class[] paramTypes = candidate.getParameterTypes();
 						String[] paramNames = null;
 						ParameterNameDiscoverer pnd = getParameterNameDiscoverer();
 						if (pnd != null) {
-							paramNames = pnd.getParameterNames(factoryMethod);
+							paramNames = pnd.getParameterNames(candidate);
 						}
 						ConstructorArgumentValues cav = mbd.getConstructorArgumentValues();
 						Set usedValueHolders =
@@ -711,10 +722,15 @@ protected Class getTypeForFactoryMethod(String beanName, RootBeanDefinition m
 							}
 						}
 						Class returnType = AutowireUtils.resolveReturnTypeForFactoryMethod(
-								factoryMethod, args, getBeanClassLoader());
+								candidate, args, getBeanClassLoader());
 						if (returnType != null) {
-							cache = true;
+							uniqueCandidate = (commonType == null && returnType == candidate.getReturnType() ?
+									candidate : null);
 							commonType = ClassUtils.determineCommonAncestor(returnType, commonType);
+							if (commonType == null) {
+								// Ambiguous return types found: return null to indicate "not determinable".
+								return null;
+							}
 						}
 					}
 					catch (Throwable ex) {
@@ -724,22 +740,25 @@ protected Class getTypeForFactoryMethod(String beanName, RootBeanDefinition m
 					}
 				}
 				else {
-					commonType = ClassUtils.determineCommonAncestor(factoryMethod.getReturnType(), commonType);
+					uniqueCandidate = (commonType == null ? candidate : null);
+					commonType = ClassUtils.determineCommonAncestor(candidate.getReturnType(), commonType);
+					if (commonType == null) {
+						// Ambiguous return types found: return null to indicate "not determinable".
+						return null;
+					}
 				}
 			}
 		}
 
-		if (commonType != null) {
-			// Clear return type found: all factory methods return same type.
-			if (cache) {
-				mbd.resolvedFactoryMethodReturnType = commonType;
-			}
-			return commonType;
-		}
-		else {
-			// Ambiguous return types found: return null to indicate "not determinable".
+		if (commonType == null) {
 			return null;
 		}
+		// Common return type found: all factory methods return same type. For a non-parameterized
+		// unique candidate, cache the full type declaration context of the target factory method.
+		cachedReturnType = (uniqueCandidate != null ?
+				ResolvableType.forMethodReturnType(uniqueCandidate) : ResolvableType.forClass(commonType));
+		mbd.factoryMethodReturnType = cachedReturnType;
+		return cachedReturnType.resolve();
 	}
 
 	/**
@@ -755,32 +774,21 @@ protected Class getTypeForFactoryMethod(String beanName, RootBeanDefinition m
 	 */
 	@Override
 	protected Class getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) {
-		class Holder { Class value = null; }
-		final Holder objectType = new Holder();
 		String factoryBeanName = mbd.getFactoryBeanName();
-		final String factoryMethodName = mbd.getFactoryMethodName();
+		String factoryMethodName = mbd.getFactoryMethodName();
 
 		if (factoryBeanName != null) {
 			if (factoryMethodName != null) {
-				// Try to obtain the FactoryBean's object type without instantiating it at all.
+				// Try to obtain the FactoryBean's object type from its factory method declaration
+				// without instantiating the containing bean at all.
 				BeanDefinition fbDef = getBeanDefinition(factoryBeanName);
-				if (fbDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) fbDef).hasBeanClass()) {
-					// CGLIB subclass methods hide generic parameters; look at the original user class.
-					Class fbClass = ClassUtils.getUserClass(((AbstractBeanDefinition) fbDef).getBeanClass());
-					// Find the given factory method, taking into account that in the case of
-					// @Bean methods, there may be parameters present.
-					ReflectionUtils.doWithMethods(fbClass,
-							new ReflectionUtils.MethodCallback() {
-								@Override
-								public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
-									if (method.getName().equals(factoryMethodName) &&
-											FactoryBean.class.isAssignableFrom(method.getReturnType())) {
-										objectType.value = GenericTypeResolver.resolveReturnTypeArgument(method, FactoryBean.class);
-									}
-								}
-							});
-					if (objectType.value != null && Object.class != objectType.value) {
-						return objectType.value;
+				if (fbDef instanceof AbstractBeanDefinition) {
+					AbstractBeanDefinition afbDef = (AbstractBeanDefinition) fbDef;
+					if (afbDef.hasBeanClass()) {
+						Class result = getTypeForFactoryBeanFromMethod(afbDef.getBeanClass(), factoryMethodName);
+						if (result != null) {
+							return result;
+						}
 					}
 				}
 			}
@@ -792,20 +800,70 @@ public void doWith(Method method) throws IllegalArgumentException, IllegalAccess
 			}
 		}
 
+		// Let's obtain a shortcut instance for an early getObjectType() call...
 		FactoryBean fb = (mbd.isSingleton() ?
 				getSingletonFactoryBeanForTypeCheck(beanName, mbd) :
 				getNonSingletonFactoryBeanForTypeCheck(beanName, mbd));
 
 		if (fb != null) {
 			// Try to obtain the FactoryBean's object type from this early stage of the instance.
-			objectType.value = getTypeForFactoryBean(fb);
-			if (objectType.value != null) {
-				return objectType.value;
+			Class result = getTypeForFactoryBean(fb);
+			if (result != null) {
+				return result;
+			}
+			else {
+				// No type found for shortcut FactoryBean instance:
+				// fall back to full creation of the FactoryBean instance.
+				return super.getTypeForFactoryBean(beanName, mbd);
 			}
 		}
 
-		// No type found - fall back to full creation of the FactoryBean instance.
-		return super.getTypeForFactoryBean(beanName, mbd);
+		if (factoryBeanName == null && mbd.hasBeanClass()) {
+			// No early bean instantiation possible: determine FactoryBean's type from
+			// static factory method signature or from class inheritance hierarchy...
+			if (factoryMethodName != null) {
+				return getTypeForFactoryBeanFromMethod(mbd.getBeanClass(), factoryMethodName);
+			}
+			else {
+				return GenericTypeResolver.resolveTypeArgument(mbd.getBeanClass(), FactoryBean.class);
+			}
+		}
+
+		return null;
+	}
+
+	/**
+	 * Introspect the factory method signatures on the given bean class,
+	 * trying to find a common {@code FactoryBean} object type declared there.
+	 * @param beanClass the bean class to find the factory method on
+	 * @param factoryMethodName the name of the factory method
+	 * @return the common {@code FactoryBean} object type, or {@code null} if none
+	 */
+	private Class getTypeForFactoryBeanFromMethod(Class beanClass, final String factoryMethodName) {
+		class Holder { Class value = null; }
+		final Holder objectType = new Holder();
+
+		// CGLIB subclass methods hide generic parameters; look at the original user class.
+		Class fbClass = ClassUtils.getUserClass(beanClass);
+
+		// Find the given factory method, taking into account that in the case of
+		// @Bean methods, there may be parameters present.
+		ReflectionUtils.doWithMethods(fbClass,
+				new ReflectionUtils.MethodCallback() {
+					@Override
+					public void doWith(Method method) {
+						if (method.getName().equals(factoryMethodName) &&
+								FactoryBean.class.isAssignableFrom(method.getReturnType())) {
+							Class currentType = GenericTypeResolver.resolveReturnTypeArgument(
+									method, FactoryBean.class);
+							if (currentType != null) {
+								objectType.value = ClassUtils.determineCommonAncestor(currentType, objectType.value);
+							}
+						}
+					}
+				});
+
+		return (objectType.value != null && Object.class != objectType.value ? objectType.value : null);
 	}
 
 	/**
@@ -824,7 +882,7 @@ protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd,
 					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
 					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
 					if (exposedObject == null) {
-						return exposedObject;
+						return null;
 					}
 				}
 			}
@@ -851,11 +909,16 @@ private FactoryBean getSingletonFactoryBeanForTypeCheck(String beanName, Root
 			if (bw != null) {
 				return (FactoryBean) bw.getWrappedInstance();
 			}
+			Object beanInstance = getSingleton(beanName, false);
+			if (beanInstance instanceof FactoryBean) {
+				return (FactoryBean) beanInstance;
+			}
 			if (isSingletonCurrentlyInCreation(beanName) ||
 					(mbd.getFactoryBeanName() != null && isSingletonCurrentlyInCreation(mbd.getFactoryBeanName()))) {
 				return null;
 			}
-			Object instance = null;
+
+			Object instance;
 			try {
 				// Mark this bean as currently in creation, even if just partially.
 				beforeSingletonCreation(beanName);
@@ -870,6 +933,7 @@ private FactoryBean getSingletonFactoryBeanForTypeCheck(String beanName, Root
 				// Finished partial creation of this bean.
 				afterSingletonCreation(beanName);
 			}
+
 			FactoryBean fb = getFactoryBean(beanName, instance);
 			if (bw != null) {
 				this.factoryBeanInstanceCache.put(beanName, bw);
@@ -890,6 +954,7 @@ private FactoryBean getNonSingletonFactoryBeanForTypeCheck(String beanName, R
 		if (isPrototypeCurrentlyInCreation(beanName)) {
 			return null;
 		}
+
 		Object instance = null;
 		try {
 			// Mark this bean as currently in creation, even if just partially.
@@ -913,6 +978,7 @@ private FactoryBean getNonSingletonFactoryBeanForTypeCheck(String beanName, R
 			// Finished partial creation of this bean.
 			afterPrototypeCreation(beanName);
 		}
+
 		return getFactoryBean(beanName, instance);
 	}
 
@@ -922,24 +988,15 @@ private FactoryBean getNonSingletonFactoryBeanForTypeCheck(String beanName, R
 	 * @param mbd the merged bean definition for the bean
 	 * @param beanType the actual type of the managed bean instance
 	 * @param beanName the name of the bean
-	 * @throws BeansException if any post-processing failed
 	 * @see MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
 	 */
-	protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class beanType, String beanName)
-			throws BeansException {
-
-		try {
-			for (BeanPostProcessor bp : getBeanPostProcessors()) {
-				if (bp instanceof MergedBeanDefinitionPostProcessor) {
-					MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp;
-					bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName);
-				}
+	protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class beanType, String beanName) {
+		for (BeanPostProcessor bp : getBeanPostProcessors()) {
+			if (bp instanceof MergedBeanDefinitionPostProcessor) {
+				MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp;
+				bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName);
 			}
 		}
-		catch (Exception ex) {
-			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
-					"Post-processing failed of bean type [" + beanType + "] failed", ex);
-		}
 	}
 
 	/**
@@ -976,12 +1033,9 @@ protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition
 	 * @param beanClass the class of the bean to be instantiated
 	 * @param beanName the name of the bean
 	 * @return the bean object to use instead of a default instance of the target bean, or {@code null}
-	 * @throws BeansException if any post-processing failed
 	 * @see InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
 	 */
-	protected Object applyBeanPostProcessorsBeforeInstantiation(Class beanClass, String beanName)
-			throws BeansException {
-
+	protected Object applyBeanPostProcessorsBeforeInstantiation(Class beanClass, String beanName) {
 		for (BeanPostProcessor bp : getBeanPostProcessors()) {
 			if (bp instanceof InstantiationAwareBeanPostProcessor) {
 				InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
@@ -1000,7 +1054,7 @@ protected Object applyBeanPostProcessorsBeforeInstantiation(Class beanClass,
 	 * @param beanName the name of the bean
 	 * @param mbd the bean definition for the bean
 	 * @param args explicit arguments to use for constructor or factory method invocation
-	 * @return BeanWrapper for the new instance
+	 * @return a BeanWrapper for the new instance
 	 * @see #instantiateUsingFactoryMethod
 	 * @see #autowireConstructor
 	 * @see #instantiateBean
@@ -1080,7 +1134,7 @@ protected Constructor[] determineConstructorsFromBeanPostProcessors(Class
 	 * Instantiate the given bean using its default constructor.
 	 * @param beanName the name of the bean
 	 * @param mbd the bean definition for the bean
-	 * @return BeanWrapper for the new instance
+	 * @return a BeanWrapper for the new instance
 	 */
 	protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
 		try {
@@ -1102,7 +1156,8 @@ public Object run() {
 			return bw;
 		}
 		catch (Throwable ex) {
-			throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);
+			throw new BeanCreationException(
+					mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);
 		}
 	}
 
@@ -1114,7 +1169,7 @@ public Object run() {
 	 * @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)
-	 * @return BeanWrapper for the new instance
+	 * @return a BeanWrapper for the new instance
 	 * @see #getBean(String, Object[])
 	 */
 	protected BeanWrapper instantiateUsingFactoryMethod(
@@ -1135,7 +1190,7 @@ protected BeanWrapper instantiateUsingFactoryMethod(
 	 * @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)
-	 * @return BeanWrapper for the new instance
+	 * @return a BeanWrapper for the new instance
 	 */
 	protected BeanWrapper autowireConstructor(
 			String beanName, RootBeanDefinition mbd, Constructor[] ctors, Object[] explicitArgs) {
@@ -1148,7 +1203,7 @@ protected BeanWrapper autowireConstructor(
 	 * from the bean definition.
 	 * @param beanName the name of the bean
 	 * @param mbd the bean definition for the bean
-	 * @param bw BeanWrapper with bean instance
+	 * @param bw the BeanWrapper with bean instance
 	 */
 	protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {
 		PropertyValues pvs = mbd.getPropertyValues();
@@ -1232,7 +1287,7 @@ protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper
 	 * @param beanName the name of the bean we're wiring up.
 	 * Useful for debugging messages; not used functionally.
 	 * @param mbd bean definition to update through autowiring
-	 * @param bw BeanWrapper from which we can obtain information about the bean
+	 * @param bw the BeanWrapper from which we can obtain information about the bean
 	 * @param pvs the PropertyValues to register wired objects with
 	 */
 	protected void autowireByName(
@@ -1266,7 +1321,7 @@ protected void autowireByName(
 	 * behavior for bigger applications.
 	 * @param beanName the name of the bean to autowire by type
 	 * @param mbd the merged bean definition to update through autowiring
-	 * @param bw BeanWrapper from which we can obtain information about the bean
+	 * @param bw the BeanWrapper from which we can obtain information about the bean
 	 * @param pvs the PropertyValues to register wired objects with
 	 */
 	protected void autowireByType(
@@ -1365,7 +1420,7 @@ protected PropertyDescriptor[] filterPropertyDescriptorsForDependencyCheck(BeanW
 	 */
 	protected PropertyDescriptor[] filterPropertyDescriptorsForDependencyCheck(BeanWrapper bw) {
 		List pds =
-				new LinkedList(Arrays.asList(bw.getPropertyDescriptors()));
+				new ArrayList(Arrays.asList(bw.getPropertyDescriptors()));
 		for (Iterator it = pds.iterator(); it.hasNext();) {
 			PropertyDescriptor pd = it.next();
 			if (isExcludedFromDependencyCheck(pd)) {
@@ -1434,15 +1489,13 @@ protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrap
 			return;
 		}
 
+		if (System.getSecurityManager() != null && bw instanceof BeanWrapperImpl) {
+			((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext());
+		}
+
 		MutablePropertyValues mpvs = null;
 		List original;
 
-		if (System.getSecurityManager() != null) {
-			if (bw instanceof BeanWrapperImpl) {
-				((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext());
-			}
-		}
-
 		if (pvs instanceof MutablePropertyValues) {
 			mpvs = (MutablePropertyValues) pvs;
 			if (mpvs.isConverted()) {
@@ -1578,7 +1631,6 @@ public Object run() {
 					(mbd != null ? mbd.getResourceDescription() : null),
 					beanName, "Invocation of init method failed", ex);
 		}
-
 		if (mbd == null || !mbd.isSynthetic()) {
 			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
 		}
@@ -1654,7 +1706,9 @@ public Object run() throws Exception {
 	 * methods with arguments.
 	 * @see #invokeInitMethods
 	 */
-	protected void invokeCustomInitMethod(String beanName, final Object bean, RootBeanDefinition mbd) throws Throwable {
+	protected void invokeCustomInitMethod(String beanName, final Object bean, RootBeanDefinition mbd)
+			throws Throwable {
+
 		String initMethodName = mbd.getInitMethodName();
 		final Method initMethod = (mbd.isNonPublicAccessAllowed() ?
 				BeanUtils.findMethod(bean.getClass(), initMethodName) :
@@ -1728,8 +1782,21 @@ protected Object postProcessObjectFromFactoryBean(Object object, String beanName
 	 */
 	@Override
 	protected void removeSingleton(String beanName) {
-		super.removeSingleton(beanName);
-		this.factoryBeanInstanceCache.remove(beanName);
+		synchronized (getSingletonMutex()) {
+			super.removeSingleton(beanName);
+			this.factoryBeanInstanceCache.remove(beanName);
+		}
+	}
+
+	/**
+	 * Overridden to clear FactoryBean instance cache as well.
+	 */
+	@Override
+	protected void clearSingletonCache() {
+		synchronized (getSingletonMutex()) {
+			super.clearSingletonCache();
+			this.factoryBeanInstanceCache.clear();
+		}
 	}
 
 
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 a0d7dc21dd21..1ce334fbf556 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
@@ -48,6 +48,7 @@
  * @author Juergen Hoeller
  * @author Rob Harrop
  * @author Mark Fisher
+ * @see GenericBeanDefinition
  * @see RootBeanDefinition
  * @see ChildBeanDefinition
  */
@@ -159,16 +160,16 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
 
 	private boolean lenientConstructorResolution = true;
 
+	private String factoryBeanName;
+
+	private String factoryMethodName;
+
 	private ConstructorArgumentValues constructorArgumentValues;
 
 	private MutablePropertyValues propertyValues;
 
 	private MethodOverrides methodOverrides = new MethodOverrides();
 
-	private String factoryBeanName;
-
-	private String factoryMethodName;
-
 	private String initMethodName;
 
 	private String destroyMethodName;
@@ -210,14 +211,14 @@ protected AbstractBeanDefinition(ConstructorArgumentValues cargs, MutablePropert
 	protected AbstractBeanDefinition(BeanDefinition original) {
 		setParentName(original.getParentName());
 		setBeanClassName(original.getBeanClassName());
-		setFactoryBeanName(original.getFactoryBeanName());
-		setFactoryMethodName(original.getFactoryMethodName());
 		setScope(original.getScope());
 		setAbstract(original.isAbstract());
 		setLazyInit(original.isLazyInit());
-		setRole(original.getRole());
+		setFactoryBeanName(original.getFactoryBeanName());
+		setFactoryMethodName(original.getFactoryMethodName());
 		setConstructorArgumentValues(new ConstructorArgumentValues(original.getConstructorArgumentValues()));
 		setPropertyValues(new MutablePropertyValues(original.getPropertyValues()));
+		setRole(original.getRole());
 		setSource(original.getSource());
 		copyAttributesFrom(original);
 
@@ -230,15 +231,15 @@ protected AbstractBeanDefinition(BeanDefinition original) {
 			setDependencyCheck(originalAbd.getDependencyCheck());
 			setDependsOn(originalAbd.getDependsOn());
 			setAutowireCandidate(originalAbd.isAutowireCandidate());
-			copyQualifiersFrom(originalAbd);
 			setPrimary(originalAbd.isPrimary());
+			copyQualifiersFrom(originalAbd);
 			setNonPublicAccessAllowed(originalAbd.isNonPublicAccessAllowed());
 			setLenientConstructorResolution(originalAbd.isLenientConstructorResolution());
+			setMethodOverrides(new MethodOverrides(originalAbd.getMethodOverrides()));
 			setInitMethodName(originalAbd.getInitMethodName());
 			setEnforceInitMethod(originalAbd.isEnforceInitMethod());
 			setDestroyMethodName(originalAbd.getDestroyMethodName());
 			setEnforceDestroyMethod(originalAbd.isEnforceDestroyMethod());
-			setMethodOverrides(new MethodOverrides(originalAbd.getMethodOverrides()));
 			setSynthetic(originalAbd.isSynthetic());
 			setResource(originalAbd.getResource());
 		}
@@ -268,20 +269,20 @@ public void overrideFrom(BeanDefinition other) {
 		if (StringUtils.hasLength(other.getBeanClassName())) {
 			setBeanClassName(other.getBeanClassName());
 		}
+		if (StringUtils.hasLength(other.getScope())) {
+			setScope(other.getScope());
+		}
+		setAbstract(other.isAbstract());
+		setLazyInit(other.isLazyInit());
 		if (StringUtils.hasLength(other.getFactoryBeanName())) {
 			setFactoryBeanName(other.getFactoryBeanName());
 		}
 		if (StringUtils.hasLength(other.getFactoryMethodName())) {
 			setFactoryMethodName(other.getFactoryMethodName());
 		}
-		if (StringUtils.hasLength(other.getScope())) {
-			setScope(other.getScope());
-		}
-		setAbstract(other.isAbstract());
-		setLazyInit(other.isLazyInit());
-		setRole(other.getRole());
 		getConstructorArgumentValues().addArgumentValues(other.getConstructorArgumentValues());
 		getPropertyValues().addPropertyValues(other.getPropertyValues());
+		setRole(other.getRole());
 		setSource(other.getSource());
 		copyAttributesFrom(other);
 
@@ -290,14 +291,15 @@ public void overrideFrom(BeanDefinition other) {
 			if (otherAbd.hasBeanClass()) {
 				setBeanClass(otherAbd.getBeanClass());
 			}
-			setAutowireCandidate(otherAbd.isAutowireCandidate());
 			setAutowireMode(otherAbd.getAutowireMode());
-			copyQualifiersFrom(otherAbd);
-			setPrimary(otherAbd.isPrimary());
 			setDependencyCheck(otherAbd.getDependencyCheck());
 			setDependsOn(otherAbd.getDependsOn());
+			setAutowireCandidate(otherAbd.isAutowireCandidate());
+			setPrimary(otherAbd.isPrimary());
+			copyQualifiersFrom(otherAbd);
 			setNonPublicAccessAllowed(otherAbd.isNonPublicAccessAllowed());
 			setLenientConstructorResolution(otherAbd.isLenientConstructorResolution());
+			getMethodOverrides().addOverrides(otherAbd.getMethodOverrides());
 			if (StringUtils.hasLength(otherAbd.getInitMethodName())) {
 				setInitMethodName(otherAbd.getInitMethodName());
 				setEnforceInitMethod(otherAbd.isEnforceInitMethod());
@@ -306,7 +308,6 @@ public void overrideFrom(BeanDefinition other) {
 				setDestroyMethodName(otherAbd.getDestroyMethodName());
 				setEnforceDestroyMethod(otherAbd.isEnforceDestroyMethod());
 			}
-			getMethodOverrides().addOverrides(otherAbd.getMethodOverrides());
 			setSynthetic(otherAbd.isSynthetic());
 			setResource(otherAbd.getResource());
 		}
@@ -331,10 +332,25 @@ public void applyDefaults(BeanDefinitionDefaults defaults) {
 
 
 	/**
-	 * Return whether this definition specifies a bean class.
+	 * Specify the bean class name of this bean definition.
 	 */
-	public boolean hasBeanClass() {
-		return (this.beanClass instanceof Class);
+	@Override
+	public void setBeanClassName(String beanClassName) {
+		this.beanClass = beanClassName;
+	}
+
+	/**
+	 * Return the current bean class name of this bean definition.
+	 */
+	@Override
+	public String getBeanClassName() {
+		Object beanClassObject = this.beanClass;
+		if (beanClassObject instanceof Class) {
+			return ((Class) beanClassObject).getName();
+		}
+		else {
+			return (String) beanClassObject;
+		}
 	}
 
 	/**
@@ -362,20 +378,11 @@ public Class getBeanClass() throws IllegalStateException {
 		return (Class) beanClassObject;
 	}
 
-	@Override
-	public void setBeanClassName(String beanClassName) {
-		this.beanClass = beanClassName;
-	}
-
-	@Override
-	public String getBeanClassName() {
-		Object beanClassObject = this.beanClass;
-		if (beanClassObject instanceof Class) {
-			return ((Class) beanClassObject).getName();
-		}
-		else {
-			return (String) beanClassObject;
-		}
+	/**
+	 * Return whether this definition specifies a bean class.
+	 */
+	public boolean hasBeanClass() {
+		return (this.beanClass instanceof Class);
 	}
 
 	/**
@@ -396,7 +403,6 @@ public Class resolveBeanClass(ClassLoader classLoader) throws ClassNotFoundEx
 		return resolvedClass;
 	}
 
-
 	/**
 	 * Set the name of the target scope for the bean.
 	 * 

The default is singleton status, although this is only applied once @@ -478,7 +484,6 @@ public boolean isLazyInit() { return this.lazyInit; } - /** * Set the autowire mode. This determines whether any automagical detection * and setting of bean references will happen. Default is AUTOWIRE_NO, @@ -569,6 +574,12 @@ public String[] getDependsOn() { /** * Set whether this bean is a candidate for getting autowired into some other bean. + *

Note that this flag is designed to only affect type-based autowiring. + * It does not affect explicit references by name, which will get resolved even + * if the specified bean is not marked as an autowire candidate. As a consequence, + * autowiring by name will nevertheless inject a bean if the name matches. + * @see #AUTOWIRE_BY_TYPE + * @see #AUTOWIRE_BY_NAME */ @Override public void setAutowireCandidate(boolean autowireCandidate) { @@ -585,7 +596,7 @@ public boolean isAutowireCandidate() { /** * Set whether this bean is a primary autowire candidate. - * If this value is true for exactly one bean among multiple + *

If this value is {@code true} for exactly one bean among multiple * matching candidates, it will serve as a tie-breaker. */ @Override @@ -595,8 +606,6 @@ public void setPrimary(boolean primary) { /** * Return whether this bean is a primary autowire candidate. - * If this value is true for exactly one bean among multiple - * matching candidates, it will serve as a tie-breaker. */ @Override public boolean isPrimary() { @@ -643,7 +652,6 @@ public void copyQualifiersFrom(AbstractBeanDefinition source) { this.qualifiers.putAll(source.qualifiers); } - /** * Specify whether to allow access to non-public constructors and methods, * for the case of externalized metadata pointing to those. The default is @@ -683,6 +691,45 @@ public boolean isLenientConstructorResolution() { return this.lenientConstructorResolution; } + /** + * Specify the factory bean to use, if any. + * This the name of the bean to call the specified factory method on. + * @see #setFactoryMethodName + */ + @Override + public void setFactoryBeanName(String factoryBeanName) { + this.factoryBeanName = factoryBeanName; + } + + /** + * Return the factory bean name, if any. + */ + @Override + public String getFactoryBeanName() { + return this.factoryBeanName; + } + + /** + * Specify a factory method, if any. This method will be invoked with + * constructor arguments, or with no arguments if none are specified. + * The method will be invoked on the specified factory bean, if any, + * or otherwise as a static method on the local bean class. + * @see #setFactoryBeanName + * @see #setBeanClassName + */ + @Override + public void setFactoryMethodName(String factoryMethodName) { + this.factoryMethodName = factoryMethodName; + } + + /** + * Return a factory method, if any. + */ + @Override + public String getFactoryMethodName() { + return this.factoryMethodName; + } + /** * Specify constructor argument values for this bean. */ @@ -737,27 +784,6 @@ public MethodOverrides getMethodOverrides() { return this.methodOverrides; } - - @Override - public void setFactoryBeanName(String factoryBeanName) { - this.factoryBeanName = factoryBeanName; - } - - @Override - public String getFactoryBeanName() { - return this.factoryBeanName; - } - - @Override - public void setFactoryMethodName(String factoryMethodName) { - this.factoryMethodName = factoryMethodName; - } - - @Override - public String getFactoryMethodName() { - return this.factoryMethodName; - } - /** * Set the name of the initializer method. The default is {@code null} * in which case there is no initializer method. @@ -822,7 +848,6 @@ public boolean isEnforceDestroyMethod() { return this.enforceDestroyMethod; } - /** * Set whether this bean definition is 'synthetic', that is, not defined * by the application itself (for example, an infrastructure bean such @@ -855,7 +880,6 @@ public int getRole() { return this.role; } - /** * Set a human-readable description of this bean definition. */ @@ -863,6 +887,9 @@ public void setDescription(String description) { this.description = description; } + /** + * Return a human-readable description of this bean definition. + */ @Override public String getDescription() { return this.description; @@ -891,6 +918,10 @@ public void setResourceDescription(String resourceDescription) { this.resource = new DescriptiveResource(resourceDescription); } + /** + * Return a description of the resource that this bean definition + * came from (for the purpose of showing context in case of errors). + */ @Override public String getResourceDescription() { return (this.resource != null ? this.resource.getDescription() : null); @@ -903,6 +934,12 @@ public void setOriginatingBeanDefinition(BeanDefinition originatingBd) { this.resource = new BeanDefinitionResource(originatingBd); } + /** + * Return the originating BeanDefinition, or {@code null} if none. + * Allows for retrieving the decorated bean definition, if any. + *

Note that this method returns the immediate originator. Iterate through the + * originator chain to find the original BeanDefinition as defined by the user. + */ @Override public BeanDefinition getOriginatingBeanDefinition() { return (this.resource instanceof BeanDefinitionResource ? @@ -981,7 +1018,6 @@ public Object clone() { */ public abstract AbstractBeanDefinition cloneBeanDefinition(); - @Override public boolean equals(Object other) { if (this == other) { 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 62dfa38e42e8..1e021cc904f7 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-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -134,13 +134,13 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp private final Set propertyEditorRegistrars = new LinkedHashSet(4); - /** A custom TypeConverter to use, overriding the default PropertyEditor mechanism */ - private TypeConverter typeConverter; - /** Custom PropertyEditors to apply to the beans of this factory */ private final Map, Class> customEditors = new HashMap, Class>(4); + /** A custom TypeConverter to use, overriding the default PropertyEditor mechanism */ + private TypeConverter typeConverter; + /** String resolvers to apply e.g. to annotation attribute values */ private final List embeddedValueResolvers = new LinkedList(); @@ -287,13 +287,19 @@ protected T doGetBean( // Guarantee initialization of beans that the current bean depends on. String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { - for (String dependsOnBean : dependsOn) { - if (isDependent(beanName, dependsOnBean)) { + for (String dep : dependsOn) { + if (isDependent(beanName, dep)) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, - "Circular depends-on relationship between '" + beanName + "' and '" + dependsOnBean + "'"); + "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); + } + registerDependentBean(dep, beanName); + try { + getBean(dep); + } + catch (NoSuchBeanDefinitionException ex) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "'" + beanName + "' depends on missing bean '" + dep + "'", ex); } - registerDependentBean(dependsOnBean, beanName); - getBean(dependsOnBean); } } @@ -366,14 +372,14 @@ public Object getObject() throws BeansException { } // Check if required type matches the type of the actual bean instance. - if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) { + if (requiredType != null && bean != null && !requiredType.isInstance(bean)) { try { return getTypeConverter().convertIfNecessary(bean, requiredType); } catch (TypeMismatchException ex) { if (logger.isDebugEnabled()) { - logger.debug("Failed to convert bean '" + name + "' to required type [" + - ClassUtils.getQualifiedName(requiredType) + "]", ex); + logger.debug("Failed to convert bean '" + name + "' to required type '" + + ClassUtils.getQualifiedName(requiredType) + "'", ex); } throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } @@ -409,33 +415,31 @@ else if (containsSingleton(beanName)) { return true; } - else { - // No singleton instance found -> check bean definition. - BeanFactory parentBeanFactory = getParentBeanFactory(); - if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { - // No bean definition found in this factory -> delegate to parent. - return parentBeanFactory.isSingleton(originalBeanName(name)); - } + // No singleton instance found -> check bean definition. + BeanFactory parentBeanFactory = getParentBeanFactory(); + if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { + // No bean definition found in this factory -> delegate to parent. + return parentBeanFactory.isSingleton(originalBeanName(name)); + } - RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); - // In case of FactoryBean, return singleton status of created object if not a dereference. - if (mbd.isSingleton()) { - if (isFactoryBean(beanName, mbd)) { - if (BeanFactoryUtils.isFactoryDereference(name)) { - return true; - } - FactoryBean factoryBean = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName); - return factoryBean.isSingleton(); - } - else { - return !BeanFactoryUtils.isFactoryDereference(name); + // In case of FactoryBean, return singleton status of created object if not a dereference. + if (mbd.isSingleton()) { + if (isFactoryBean(beanName, mbd)) { + if (BeanFactoryUtils.isFactoryDereference(name)) { + return true; } + FactoryBean factoryBean = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName); + return factoryBean.isSingleton(); } else { - return false; + return !BeanFactoryUtils.isFactoryDereference(name); } } + else { + return false; + } } @Override @@ -453,32 +457,31 @@ public boolean isPrototype(String name) throws NoSuchBeanDefinitionException { // In case of FactoryBean, return singleton status of created object if not a dereference. return (!BeanFactoryUtils.isFactoryDereference(name) || isFactoryBean(beanName, mbd)); } - else { - // Singleton or scoped - not a prototype. - // However, FactoryBean may still produce a prototype object... - if (BeanFactoryUtils.isFactoryDereference(name)) { - return false; - } - if (isFactoryBean(beanName, mbd)) { - final FactoryBean factoryBean = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName); - if (System.getSecurityManager() != null) { - return AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Boolean run() { - return ((factoryBean instanceof SmartFactoryBean && ((SmartFactoryBean) factoryBean).isPrototype()) || - !factoryBean.isSingleton()); - } - }, getAccessControlContext()); - } - else { - return ((factoryBean instanceof SmartFactoryBean && ((SmartFactoryBean) factoryBean).isPrototype()) || - !factoryBean.isSingleton()); - } + + // Singleton or scoped - not a prototype. + // However, FactoryBean may still produce a prototype object... + if (BeanFactoryUtils.isFactoryDereference(name)) { + return false; + } + if (isFactoryBean(beanName, mbd)) { + final FactoryBean fb = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName); + if (System.getSecurityManager() != null) { + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Boolean run() { + return ((fb instanceof SmartFactoryBean && ((SmartFactoryBean) fb).isPrototype()) || + !fb.isSingleton()); + } + }, getAccessControlContext()); } else { - return false; + return ((fb instanceof SmartFactoryBean && ((SmartFactoryBean) fb).isPrototype()) || + !fb.isSingleton()); } } + else { + return false; + } } @Override @@ -497,68 +500,100 @@ public boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuc return typeToMatch.isInstance(beanInstance); } } - else { - return (!BeanFactoryUtils.isFactoryDereference(name) && typeToMatch.isInstance(beanInstance)); + else if (!BeanFactoryUtils.isFactoryDereference(name)) { + if (typeToMatch.isInstance(beanInstance)) { + // Direct match for exposed instance? + return true; + } + else if (typeToMatch.hasGenerics() && containsBeanDefinition(beanName)) { + // Generics potentially only match on the target class, not on the proxy... + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + Class targetType = mbd.getTargetType(); + if (targetType != null && targetType != ClassUtils.getUserClass(beanInstance)) { + // Check raw class match as well, making sure it's exposed on the proxy. + Class classToMatch = typeToMatch.resolve(); + if (classToMatch != null && !classToMatch.isInstance(beanInstance)) { + return false; + } + if (typeToMatch.isAssignableFrom(targetType)) { + return true; + } + } + ResolvableType resolvableType = mbd.targetType; + if (resolvableType == null) { + resolvableType = mbd.factoryMethodReturnType; + } + return (resolvableType != null && typeToMatch.isAssignableFrom(resolvableType)); + } } + return false; } else if (containsSingleton(beanName) && !containsBeanDefinition(beanName)) { // null instance registered return false; } - else { - // No singleton instance found -> check bean definition. - BeanFactory parentBeanFactory = getParentBeanFactory(); - if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { - // No bean definition found in this factory -> delegate to parent. - return parentBeanFactory.isTypeMatch(originalBeanName(name), typeToMatch); - } + // No singleton instance found -> check bean definition. + BeanFactory parentBeanFactory = getParentBeanFactory(); + if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { + // No bean definition found in this factory -> delegate to parent. + return parentBeanFactory.isTypeMatch(originalBeanName(name), typeToMatch); + } - // Retrieve corresponding bean definition. - RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); - - Class classToMatch = typeToMatch.getRawClass(); - Class[] typesToMatch = (FactoryBean.class == classToMatch ? - new Class[] {classToMatch} : new Class[] {FactoryBean.class, classToMatch}); - - // Check decorated bean definition, if any: We assume it'll be easier - // to determine the decorated bean's type than the proxy's type. - BeanDefinitionHolder dbd = mbd.getDecoratedDefinition(); - if (dbd != null && !BeanFactoryUtils.isFactoryDereference(name)) { - RootBeanDefinition tbd = getMergedBeanDefinition(dbd.getBeanName(), dbd.getBeanDefinition(), mbd); - Class targetClass = predictBeanType(dbd.getBeanName(), tbd, typesToMatch); - if (targetClass != null && !FactoryBean.class.isAssignableFrom(targetClass)) { - return typeToMatch.isAssignableFrom(targetClass); - } - } + // Retrieve corresponding bean definition. + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); - Class beanType = predictBeanType(beanName, mbd, typesToMatch); - if (beanType == null) { - return false; + Class classToMatch = typeToMatch.resolve(); + if (classToMatch == null) { + classToMatch = FactoryBean.class; + } + Class[] typesToMatch = (FactoryBean.class == classToMatch ? + new Class[] {classToMatch} : new Class[] {FactoryBean.class, classToMatch}); + + // Check decorated bean definition, if any: We assume it'll be easier + // to determine the decorated bean's type than the proxy's type. + BeanDefinitionHolder dbd = mbd.getDecoratedDefinition(); + if (dbd != null && !BeanFactoryUtils.isFactoryDereference(name)) { + RootBeanDefinition tbd = getMergedBeanDefinition(dbd.getBeanName(), dbd.getBeanDefinition(), mbd); + Class targetClass = predictBeanType(dbd.getBeanName(), tbd, typesToMatch); + if (targetClass != null && !FactoryBean.class.isAssignableFrom(targetClass)) { + return typeToMatch.isAssignableFrom(targetClass); } + } - // Check bean class whether we're dealing with a FactoryBean. - if (FactoryBean.class.isAssignableFrom(beanType)) { - if (!BeanFactoryUtils.isFactoryDereference(name)) { - // If it's a FactoryBean, we want to look at what it creates, not the factory class. - beanType = getTypeForFactoryBean(beanName, mbd); - if (beanType == null) { - return false; - } - } - } - else if (BeanFactoryUtils.isFactoryDereference(name)) { - // Special case: A SmartInstantiationAwareBeanPostProcessor returned a non-FactoryBean - // type but we nevertheless are being asked to dereference a FactoryBean... - // Let's check the original bean class and proceed with it if it is a FactoryBean. - beanType = predictBeanType(beanName, mbd, FactoryBean.class); - if (beanType == null || !FactoryBean.class.isAssignableFrom(beanType)) { + Class beanType = predictBeanType(beanName, mbd, typesToMatch); + if (beanType == null) { + return false; + } + + // Check bean class whether we're dealing with a FactoryBean. + if (FactoryBean.class.isAssignableFrom(beanType)) { + if (!BeanFactoryUtils.isFactoryDereference(name)) { + // If it's a FactoryBean, we want to look at what it creates, not the factory class. + beanType = getTypeForFactoryBean(beanName, mbd); + if (beanType == null) { return false; } } + } + else if (BeanFactoryUtils.isFactoryDereference(name)) { + // Special case: A SmartInstantiationAwareBeanPostProcessor returned a non-FactoryBean + // type but we nevertheless are being asked to dereference a FactoryBean... + // Let's check the original bean class and proceed with it if it is a FactoryBean. + beanType = predictBeanType(beanName, mbd, FactoryBean.class); + if (beanType == null || !FactoryBean.class.isAssignableFrom(beanType)) { + return false; + } + } - return typeToMatch.isAssignableFrom(beanType); + ResolvableType resolvableType = mbd.targetType; + if (resolvableType == null) { + resolvableType = mbd.factoryMethodReturnType; + } + if (resolvableType != null && resolvableType.resolve() == beanType) { + return typeToMatch.isAssignableFrom(resolvableType); } + return typeToMatch.isAssignableFrom(beanType); } @Override @@ -585,43 +620,41 @@ else if (containsSingleton(beanName) && !containsBeanDefinition(beanName)) { return null; } - else { - // No singleton instance found -> check bean definition. - BeanFactory parentBeanFactory = getParentBeanFactory(); - if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { - // No bean definition found in this factory -> delegate to parent. - return parentBeanFactory.getType(originalBeanName(name)); - } + // No singleton instance found -> check bean definition. + BeanFactory parentBeanFactory = getParentBeanFactory(); + if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { + // No bean definition found in this factory -> delegate to parent. + return parentBeanFactory.getType(originalBeanName(name)); + } - RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); - // Check decorated bean definition, if any: We assume it'll be easier - // to determine the decorated bean's type than the proxy's type. - BeanDefinitionHolder dbd = mbd.getDecoratedDefinition(); - if (dbd != null && !BeanFactoryUtils.isFactoryDereference(name)) { - RootBeanDefinition tbd = getMergedBeanDefinition(dbd.getBeanName(), dbd.getBeanDefinition(), mbd); - Class targetClass = predictBeanType(dbd.getBeanName(), tbd); - if (targetClass != null && !FactoryBean.class.isAssignableFrom(targetClass)) { - return targetClass; - } + // Check decorated bean definition, if any: We assume it'll be easier + // to determine the decorated bean's type than the proxy's type. + BeanDefinitionHolder dbd = mbd.getDecoratedDefinition(); + if (dbd != null && !BeanFactoryUtils.isFactoryDereference(name)) { + RootBeanDefinition tbd = getMergedBeanDefinition(dbd.getBeanName(), dbd.getBeanDefinition(), mbd); + Class targetClass = predictBeanType(dbd.getBeanName(), tbd); + if (targetClass != null && !FactoryBean.class.isAssignableFrom(targetClass)) { + return targetClass; } + } - Class beanClass = predictBeanType(beanName, mbd); + Class beanClass = predictBeanType(beanName, mbd); - // Check bean class whether we're dealing with a FactoryBean. - if (beanClass != null && FactoryBean.class.isAssignableFrom(beanClass)) { - if (!BeanFactoryUtils.isFactoryDereference(name)) { - // If it's a FactoryBean, we want to look at what it creates, not at the factory class. - return getTypeForFactoryBean(beanName, mbd); - } - else { - return beanClass; - } + // Check bean class whether we're dealing with a FactoryBean. + if (beanClass != null && FactoryBean.class.isAssignableFrom(beanClass)) { + if (!BeanFactoryUtils.isFactoryDereference(name)) { + // If it's a FactoryBean, we want to look at what it creates, not at the factory class. + return getTypeForFactoryBean(beanName, mbd); } else { - return (!BeanFactoryUtils.isFactoryDereference(name) ? beanClass : null); + return beanClass; } } + else { + return (!BeanFactoryUtils.isFactoryDereference(name) ? beanClass : null); + } } @Override @@ -748,7 +781,7 @@ public Set getPropertyEditorRegistrars() { @Override public void registerCustomEditor(Class requiredType, Class propertyEditorClass) { Assert.notNull(requiredType, "Required type must not be null"); - Assert.isAssignable(PropertyEditor.class, propertyEditorClass); + Assert.notNull(propertyEditorClass, "PropertyEditor class must not be null"); this.customEditors.put(requiredType, propertyEditorClass); } @@ -805,12 +838,15 @@ public boolean hasEmbeddedValueResolver() { @Override public String resolveEmbeddedValue(String value) { + if (value == null) { + return null; + } String result = value; for (StringValueResolver resolver : this.embeddedValueResolvers) { + result = resolver.resolveStringValue(result); if (result == null) { return null; } - result = resolver.resolveStringValue(result); } return result; } @@ -918,10 +954,12 @@ public void copyConfigurationFrom(ConfigurableBeanFactory otherFactory) { setBeanClassLoader(otherFactory.getBeanClassLoader()); setCacheBeanMetadata(otherFactory.isCacheBeanMetadata()); setBeanExpressionResolver(otherFactory.getBeanExpressionResolver()); + setConversionService(otherFactory.getConversionService()); if (otherFactory instanceof AbstractBeanFactory) { AbstractBeanFactory otherAbstractFactory = (AbstractBeanFactory) otherFactory; - this.customEditors.putAll(otherAbstractFactory.customEditors); this.propertyEditorRegistrars.addAll(otherAbstractFactory.propertyEditorRegistrars); + this.customEditors.putAll(otherAbstractFactory.customEditors); + this.typeConverter = otherAbstractFactory.typeConverter; this.beanPostProcessors.addAll(otherAbstractFactory.beanPostProcessors); this.hasInstantiationAwareBeanPostProcessors = this.hasInstantiationAwareBeanPostProcessors || otherAbstractFactory.hasInstantiationAwareBeanPostProcessors; @@ -932,6 +970,10 @@ public void copyConfigurationFrom(ConfigurableBeanFactory otherFactory) { } else { setTypeConverter(otherFactory.getTypeConverter()); + String[] otherScopeNames = otherFactory.getRegisteredScopeNames(); + for (String scopeName : otherScopeNames) { + this.scopes.put(scopeName, otherFactory.getRegisteredScope(scopeName)); + } } } @@ -949,7 +991,6 @@ public void copyConfigurationFrom(ConfigurableBeanFactory otherFactory) { @Override public BeanDefinition getMergedBeanDefinition(String name) throws BeansException { String beanName = transformedBeanName(name); - // Efficiently check whether bean definition exists in this factory. if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory) { return ((ConfigurableBeanFactory) getParentBeanFactory()).getMergedBeanDefinition(beanName); @@ -961,7 +1002,6 @@ public BeanDefinition getMergedBeanDefinition(String name) throws BeansException @Override public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException { String beanName = transformedBeanName(name); - Object beanInstance = getSingleton(beanName, false); if (beanInstance != null) { return (beanInstance instanceof FactoryBean); @@ -970,13 +1010,11 @@ else if (containsSingleton(beanName)) { // null instance registered return false; } - // No singleton instance found -> check bean definition. if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory) { // No bean definition found in this factory -> delegate to parent. return ((ConfigurableBeanFactory) getParentBeanFactory()).isFactoryBean(name); } - return isFactoryBean(beanName, getMergedLocalBeanDefinition(beanName)); } @@ -1050,11 +1088,11 @@ public void destroyBean(String beanName, Object beanInstance) { * Destroy the given bean instance (usually a prototype instance * obtained from this factory) according to the given bean definition. * @param beanName the name of the bean definition - * @param beanInstance the bean instance to destroy + * @param bean the bean instance to destroy * @param mbd the merged bean definition */ - protected void destroyBean(String beanName, Object beanInstance, RootBeanDefinition mbd) { - new DisposableBeanAdapter(beanInstance, beanName, mbd, getBeanPostProcessors(), getAccessControlContext()).destroy(); + protected void destroyBean(String beanName, Object bean, RootBeanDefinition mbd) { + new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), getAccessControlContext()).destroy(); } @Override @@ -1235,12 +1273,13 @@ protected RootBeanDefinition getMergedBeanDefinition( pbd = getMergedBeanDefinition(parentBeanName); } else { - if (getParentBeanFactory() instanceof ConfigurableBeanFactory) { - pbd = ((ConfigurableBeanFactory) getParentBeanFactory()).getMergedBeanDefinition(parentBeanName); + BeanFactory parent = getParentBeanFactory(); + if (parent instanceof ConfigurableBeanFactory) { + pbd = ((ConfigurableBeanFactory) parent).getMergedBeanDefinition(parentBeanName); } else { - throw new NoSuchBeanDefinitionException(bd.getParentName(), - "Parent name '" + bd.getParentName() + "' is equal to bean name '" + beanName + + throw new NoSuchBeanDefinitionException(parentBeanName, + "Parent name '" + parentBeanName + "' is equal to bean name '" + beanName + "': cannot be resolved without an AbstractBeanFactory parent"); } } @@ -1267,8 +1306,8 @@ protected RootBeanDefinition getMergedBeanDefinition( mbd.setScope(containingBd.getScope()); } - // Only cache the merged bean definition if we're already about to create an - // instance of the bean, or at least have already created an instance before. + // Cache the merged bean definition for the time being + // (it might still get re-merged later on in order to pick up metadata changes) if (containingBd == null && isCacheBeanMetadata()) { this.mergedBeanDefinitions.put(beanName, mbd); } @@ -1333,6 +1372,7 @@ public void clearMetadataCache() { */ protected Class resolveBeanClass(final RootBeanDefinition mbd, String beanName, final Class... typesToMatch) throws CannotLoadBeanClassException { + try { if (mbd.hasBeanClass()) { return mbd.getBeanClass(); @@ -1361,7 +1401,9 @@ public Class run() throws Exception { } } - private Class doResolveBeanClass(RootBeanDefinition mbd, Class... typesToMatch) throws ClassNotFoundException { + private Class doResolveBeanClass(RootBeanDefinition mbd, Class... typesToMatch) + throws ClassNotFoundException { + ClassLoader beanClassLoader = getBeanClassLoader(); ClassLoader classLoaderToUse = beanClassLoader; if (!ObjectUtils.isEmpty(typesToMatch)) { @@ -1435,6 +1477,10 @@ protected Object evaluateBeanDefinitionString(String value, BeanDefinition beanD * @return the type of the bean, or {@code null} if not predictable */ protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Class... typesToMatch) { + Class targetType = mbd.getTargetType(); + if (targetType != null) { + return targetType; + } if (mbd.getFactoryMethodName() != null) { return null; } @@ -1475,7 +1521,7 @@ protected Class getTypeForFactoryBean(String beanName, RootBeanDefinition mbd return getTypeForFactoryBean(factoryBean); } catch (BeanCreationException ex) { - if (ex instanceof BeanCurrentlyInCreationException) { + if (ex.contains(BeanCurrentlyInCreationException.class)) { if (logger.isDebugEnabled()) { logger.debug("Bean currently in creation on FactoryBean type check: " + ex); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateQualifier.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateQualifier.java index f5991ac9304c..9674e5ccb6d8 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateQualifier.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateQualifier.java @@ -32,7 +32,7 @@ @SuppressWarnings("serial") public class AutowireCandidateQualifier extends BeanMetadataAttributeAccessor { - public static String VALUE_KEY = "value"; + public static final String VALUE_KEY = "value"; private final String typeName; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java index 1bf540345587..0b650df82324 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -162,8 +162,8 @@ public static Object resolveAutowiringValue(Object autowiringValue, Class req * Determine the target type for the generic return type of the given * generic factory method, where formal type variables are declared * on the given method itself. - *

For example, given a factory method with the following signature, - * if {@code resolveReturnTypeForFactoryMethod()} is invoked with the reflected + *

For example, given a factory method with the following signature, if + * {@code resolveReturnTypeForFactoryMethod()} is invoked with the reflected * method for {@code creatProxy()} and an {@code Object[]} array containing * {@code MyService.class}, {@code resolveReturnTypeForFactoryMethod()} will * infer that the target return type is {@code MyService}. @@ -184,9 +184,9 @@ public static Object resolveAutowiringValue(Object autowiringValue, Class req * @param method the method to introspect (never {@code null}) * @param args the arguments that will be supplied to the method when it is * invoked (never {@code null}) - * @param classLoader the ClassLoader to resolve class names against, if necessary - * (never {@code null}) - * @return the resolved target return type, the standard return type, or {@code null} + * @param classLoader the ClassLoader to resolve class names against, + * if necessary (never {@code null}) + * @return the resolved target return type or the standard method return type * @since 3.2.5 */ public static Class resolveReturnTypeForFactoryMethod(Method method, Object[] args, ClassLoader classLoader) { 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 a1533835f95d..6eb6ec52405b 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-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -159,13 +159,25 @@ public BeanDefinitionBuilder setParentName(String parentName) { } /** - * Set the name of the factory method to use for this definition. + * Set the name of a static factory method to use for this definition, + * to be called on this bean's class. */ public BeanDefinitionBuilder setFactoryMethod(String factoryMethod) { this.beanDefinition.setFactoryMethodName(factoryMethod); return this; } + /** + * Set the name of a non-static factory method to use for this definition, + * including the bean name of the factory instance to call the method on. + * @since 4.3.6 + */ + public BeanDefinitionBuilder setFactoryMethodOnBean(String factoryMethod, String factoryBean) { + this.beanDefinition.setFactoryMethodName(factoryMethod); + this.beanDefinition.setFactoryBeanName(factoryBean); + return this; + } + /** * Add an indexed constructor arg value. The current index is tracked internally * and all additions are at the present point. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionDefaults.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionDefaults.java index db7ccda80013..2ca5393169b3 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionDefaults.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionDefaults.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,7 +62,7 @@ public int getAutowireMode() { } public void setInitMethodName(String initMethodName) { - this.initMethodName = (StringUtils.hasText(initMethodName)) ? initMethodName : null; + this.initMethodName = (StringUtils.hasText(initMethodName) ? initMethodName : null); } public String getInitMethodName() { @@ -70,7 +70,7 @@ public String getInitMethodName() { } public void setDestroyMethodName(String destroyMethodName) { - this.destroyMethodName = (StringUtils.hasText(destroyMethodName)) ? destroyMethodName : null; + this.destroyMethodName = (StringUtils.hasText(destroyMethodName) ? destroyMethodName : null); } public String getDestroyMethodName() { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReaderUtils.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReaderUtils.java index f56f0e527aac..8a745dab2c86 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReaderUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReaderUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -69,6 +69,23 @@ public static AbstractBeanDefinition createBeanDefinition( return bd; } + /** + * Generate a bean name for the given top-level bean definition, + * unique within the given bean factory. + * @param beanDefinition the bean definition to generate a bean name for + * @param registry the bean factory that the definition is going to be + * registered with (to check for existing bean names) + * @return the generated bean name + * @throws BeanDefinitionStoreException if no unique name can be generated + * for the given bean definition + * @see #generateBeanName(BeanDefinition, BeanDefinitionRegistry, boolean) + */ + public static String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry registry) + throws BeanDefinitionStoreException { + + return generateBeanName(beanDefinition, registry, false); + } + /** * Generate a bean name for the given bean definition, unique within the * given bean factory. @@ -117,22 +134,6 @@ else if (definition.getFactoryBeanName() != null) { return id; } - /** - * Generate a bean name for the given top-level bean definition, - * unique within the given bean factory. - * @param beanDefinition the bean definition to generate a bean name for - * @param registry the bean factory that the definition is going to be - * registered with (to check for existing bean names) - * @return the generated bean name - * @throws BeanDefinitionStoreException if no unique name can be generated - * for the given bean definition - */ - public static String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry registry) - throws BeanDefinitionStoreException { - - return generateBeanName(beanDefinition, registry, false); - } - /** * Register the given bean definition with the given bean factory. * @param definitionHolder the bean definition including name and aliases diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionRegistry.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionRegistry.java index 326e1fb58c48..c99271bc32e0 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionRegistry.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,6 +55,7 @@ public interface BeanDefinitionRegistry extends AliasRegistry { * @throws BeanDefinitionStoreException if the BeanDefinition is invalid * or if there is already a BeanDefinition for the specified bean name * (and we are not allowed to override it) + * @see GenericBeanDefinition * @see RootBeanDefinition * @see ChildBeanDefinition */ diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ChildBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ChildBeanDefinition.java index 87fc66ba879c..a6204ae2169f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ChildBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ChildBeanDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,10 +53,7 @@ public class ChildBeanDefinition extends AbstractBeanDefinition { * configured through its bean properties and configuration methods. * @param parentName the name of the parent bean * @see #setBeanClass - * @see #setBeanClassName * @see #setScope - * @see #setAutowireMode - * @see #setDependencyCheck * @see #setConstructorArgumentValues * @see #setPropertyValues */ @@ -174,9 +171,7 @@ public int hashCode() { @Override public String toString() { - StringBuilder sb = new StringBuilder("Child bean with parent '"); - sb.append(this.parentName).append("': ").append(super.toString()); - return sb.toString(); + return "Child bean with parent '" + this.parentName + "': " + super.toString(); } } 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 7c871d467e82..aa485efc7130 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 @@ -855,11 +855,11 @@ static InjectionPoint setCurrentInjectionPoint(InjectionPoint injectionPoint) { */ private static class ArgumentsHolder { - public final Object rawArguments[]; + public final Object[] rawArguments; - public final Object arguments[]; + public final Object[] arguments; - public final Object preparedArguments[]; + public final Object[] preparedArguments; public boolean resolveNecessary = false; 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 bb4ef59bf860..bb5ff2cf64a3 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-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.LinkedHashMap; @@ -44,6 +43,7 @@ import java.util.concurrent.ConcurrentHashMap; import javax.inject.Provider; +import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; import org.springframework.beans.TypeConverter; import org.springframework.beans.factory.BeanCreationException; @@ -62,11 +62,13 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.SmartFactoryBean; import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.beans.factory.config.NamedBeanHolder; import org.springframework.core.OrderComparator; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; @@ -78,19 +80,17 @@ import org.springframework.util.StringUtils; /** - * Default implementation of the - * {@link org.springframework.beans.factory.ListableBeanFactory} and - * {@link BeanDefinitionRegistry} interfaces: a full-fledged bean factory - * based on bean definition objects. + * Spring's default implementation of the {@link ConfigurableListableBeanFactory} + * and {@link BeanDefinitionRegistry} interfaces: a full-fledged bean factory + * based on bean definition metadata, extensible through post-processors. * *

Typical usage is registering all bean definitions first (possibly read - * from a bean definition file), before accessing beans. Bean definition lookup + * from a bean definition file), before accessing beans. Bean lookup by name * is therefore an inexpensive operation in a local bean definition table, - * operating on pre-built bean definition metadata objects. + * operating on pre-resolved bean definition metadata objects. * - *

Can be used as a standalone bean factory, or as a superclass for custom - * bean factories. Note that readers for specific bean definition formats are - * typically implemented separately rather than as bean factory subclasses: + *

Note that readers for specific bean definition formats are typically + * implemented separately rather than as bean factory subclasses: * see for example {@link PropertiesBeanDefinitionReader} and * {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader}. * @@ -107,9 +107,10 @@ * @author Phillip Webb * @author Stephane Nicoll * @since 16 April 2001 - * @see StaticListableBeanFactory - * @see PropertiesBeanDefinitionReader - * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader + * @see #registerBeanDefinition + * @see #addBeanPostProcessor + * @see #getBean + * @see #resolveDependency */ @SuppressWarnings("serial") public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory @@ -265,6 +266,7 @@ public boolean isAllowEagerClassLoading() { /** * Set a {@link java.util.Comparator} for dependency Lists and arrays. + * @since 4.0 * @see org.springframework.core.OrderComparator * @see org.springframework.core.annotation.AnnotationAwareOrderComparator */ @@ -274,6 +276,7 @@ public void setDependencyComparator(Comparator dependencyComparator) { /** * Return the dependency comparator for this BeanFactory (may be {@code null}. + * @since 4.0 */ public Comparator getDependencyComparator() { return this.dependencyComparator; @@ -288,11 +291,10 @@ public void setAutowireCandidateResolver(final AutowireCandidateResolver autowir Assert.notNull(autowireCandidateResolver, "AutowireCandidateResolver must not be null"); if (autowireCandidateResolver instanceof BeanFactoryAware) { if (System.getSecurityManager() != null) { - final BeanFactory target = this; AccessController.doPrivileged(new PrivilegedAction() { @Override public Object run() { - ((BeanFactoryAware) autowireCandidateResolver).setBeanFactory(target); + ((BeanFactoryAware) autowireCandidateResolver).setBeanFactory(DefaultListableBeanFactory.this); return null; } }, getAccessControlContext()); @@ -319,7 +321,10 @@ public void copyConfigurationFrom(ConfigurableBeanFactory otherFactory) { DefaultListableBeanFactory otherListableFactory = (DefaultListableBeanFactory) otherFactory; this.allowBeanDefinitionOverriding = otherListableFactory.allowBeanDefinitionOverriding; this.allowEagerClassLoading = otherListableFactory.allowEagerClassLoading; - this.autowireCandidateResolver = otherListableFactory.autowireCandidateResolver; + this.dependencyComparator = otherListableFactory.dependencyComparator; + // A clone of the AutowireCandidateResolver since it is potentially BeanFactoryAware... + setAutowireCandidateResolver(BeanUtils.instantiateClass(getAutowireCandidateResolver().getClass())); + // Make resolvable dependencies (e.g. ResourceLoader) available here as well... this.resolvableDependencies.putAll(otherListableFactory.resolvableDependencies); } } @@ -336,43 +341,15 @@ public T getBean(Class requiredType) throws BeansException { @Override public T getBean(Class requiredType, Object... args) throws BeansException { - Assert.notNull(requiredType, "Required type must not be null"); - String[] beanNames = getBeanNamesForType(requiredType); - if (beanNames.length > 1) { - ArrayList autowireCandidates = new ArrayList(); - for (String beanName : beanNames) { - if (!containsBeanDefinition(beanName) || getBeanDefinition(beanName).isAutowireCandidate()) { - autowireCandidates.add(beanName); - } - } - if (autowireCandidates.size() > 0) { - beanNames = autowireCandidates.toArray(new String[autowireCandidates.size()]); - } - } - if (beanNames.length == 1) { - return getBean(beanNames[0], requiredType, args); - } - else if (beanNames.length > 1) { - Map candidates = new HashMap(); - for (String beanName : beanNames) { - candidates.put(beanName, getBean(beanName, requiredType, args)); - } - String primaryCandidate = determinePrimaryCandidate(candidates, requiredType); - if (primaryCandidate != null) { - return getBean(primaryCandidate, requiredType, args); - } - String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType); - if (priorityCandidate != null) { - return getBean(priorityCandidate, requiredType, args); - } - throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet()); - } - else if (getParentBeanFactory() != null) { - return getParentBeanFactory().getBean(requiredType, args); + NamedBeanHolder namedBean = resolveNamedBean(requiredType, args); + if (namedBean != null) { + return namedBean.getBeanInstance(); } - else { - throw new NoSuchBeanDefinitionException(requiredType); + BeanFactory parent = getParentBeanFactory(); + if (parent != null) { + return parent.getBean(requiredType, args); } + throw new NoSuchBeanDefinitionException(requiredType); } @@ -393,8 +370,9 @@ public int getBeanDefinitionCount() { @Override public String[] getBeanDefinitionNames() { - if (this.frozenBeanDefinitionNames != null) { - return this.frozenBeanDefinitionNames; + String[] frozenNames = this.frozenBeanDefinitionNames; + if (frozenNames != null) { + return frozenNames.clone(); } else { return StringUtils.toStringArray(this.beanDefinitionNames); @@ -441,12 +419,17 @@ private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSi RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); // Only check bean definition if it is complete. if (!mbd.isAbstract() && (allowEagerInit || - ((mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading())) && + (mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading()) && !requiresEagerInitForType(mbd.getFactoryBeanName()))) { // In case of FactoryBean, match object created by FactoryBean. boolean isFactoryBean = isFactoryBean(beanName, mbd); - boolean matchFound = (allowEagerInit || !isFactoryBean || containsSingleton(beanName)) && - (includeNonSingletons || isSingleton(beanName)) && isTypeMatch(beanName, type); + BeanDefinitionHolder dbd = mbd.getDecoratedDefinition(); + boolean matchFound = + (allowEagerInit || !isFactoryBean || + (dbd != null && !mbd.isLazyInit()) || containsSingleton(beanName)) && + (includeNonSingletons || + (dbd != null ? mbd.isSingleton() : isSingleton(beanName))) && + isTypeMatch(beanName, type); if (!matchFound && isFactoryBean) { // In case of FactoryBean, try to match FactoryBean instance itself next. beanName = FACTORY_BEAN_PREFIX + beanName; @@ -461,9 +444,9 @@ private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSi if (allowEagerInit) { throw ex; } - // Probably contains a placeholder: let's ignore it for type matching purposes. - if (this.logger.isDebugEnabled()) { - this.logger.debug("Ignoring bean class loading failure for bean '" + beanName + "'", ex); + // Probably a class name with a placeholder: let's ignore it for type matching purposes. + if (logger.isDebugEnabled()) { + logger.debug("Ignoring bean class loading failure for bean '" + beanName + "'", ex); } onSuppressedException(ex); } @@ -471,9 +454,9 @@ private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSi if (allowEagerInit) { throw ex; } - // Probably contains a placeholder: let's ignore it for type matching purposes. - if (this.logger.isDebugEnabled()) { - this.logger.debug("Ignoring unresolvable metadata in bean definition '" + beanName + "'", ex); + // Probably some metadata with a placeholder: let's ignore it for type matching purposes. + if (logger.isDebugEnabled()) { + logger.debug("Ignoring unresolvable metadata in bean definition '" + beanName + "'", ex); } onSuppressedException(ex); } @@ -540,8 +523,8 @@ public Map getBeansOfType(Class type, boolean includeNonSingle if (rootCause instanceof BeanCurrentlyInCreationException) { BeanCreationException bce = (BeanCreationException) rootCause; if (isCurrentlyInCreation(bce.getBeanName())) { - if (this.logger.isDebugEnabled()) { - this.logger.debug("Ignoring match to currently created bean '" + beanName + "': " + + if (logger.isDebugEnabled()) { + logger.debug("Ignoring match to currently created bean '" + beanName + "': " + ex.getMessage()); } onSuppressedException(ex); @@ -558,29 +541,29 @@ public Map getBeansOfType(Class type, boolean includeNonSingle @Override public String[] getBeanNamesForAnnotation(Class annotationType) { - List results = new ArrayList(); + List result = new ArrayList(); for (String beanName : this.beanDefinitionNames) { BeanDefinition beanDefinition = getBeanDefinition(beanName); if (!beanDefinition.isAbstract() && findAnnotationOnBean(beanName, annotationType) != null) { - results.add(beanName); + result.add(beanName); } } for (String beanName : this.manualSingletonNames) { - if (!results.contains(beanName) && findAnnotationOnBean(beanName, annotationType) != null) { - results.add(beanName); + if (!result.contains(beanName) && findAnnotationOnBean(beanName, annotationType) != null) { + result.add(beanName); } } - return results.toArray(new String[results.size()]); + return StringUtils.toStringArray(result); } @Override public Map getBeansWithAnnotation(Class annotationType) { String[] beanNames = getBeanNamesForAnnotation(annotationType); - Map results = new LinkedHashMap(beanNames.length); + Map result = new LinkedHashMap(beanNames.length); for (String beanName : beanNames) { - results.put(beanName, getBean(beanName)); + result.put(beanName, getBean(beanName)); } - return results; + return result; } /** @@ -652,13 +635,15 @@ protected boolean isAutowireCandidate(String beanName, DependencyDescriptor desc else if (containsSingleton(beanName)) { return isAutowireCandidate(beanName, new RootBeanDefinition(getType(beanName)), descriptor, resolver); } - else if (getParentBeanFactory() instanceof DefaultListableBeanFactory) { + + BeanFactory parent = getParentBeanFactory(); + if (parent instanceof DefaultListableBeanFactory) { // No bean definition found in this factory -> delegate to parent. - return ((DefaultListableBeanFactory) getParentBeanFactory()).isAutowireCandidate(beanName, descriptor, resolver); + return ((DefaultListableBeanFactory) parent).isAutowireCandidate(beanName, descriptor, resolver); } - else if (getParentBeanFactory() instanceof ConfigurableListableBeanFactory) { + else if (parent instanceof ConfigurableListableBeanFactory) { // If no DefaultListableBeanFactory, can't pass the resolver along. - return ((ConfigurableListableBeanFactory) getParentBeanFactory()).isAutowireCandidate(beanName, descriptor); + return ((ConfigurableListableBeanFactory) parent).isAutowireCandidate(beanName, descriptor); } else { return true; @@ -696,8 +681,8 @@ protected boolean isAutowireCandidate(String beanName, RootBeanDefinition mbd, public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException { BeanDefinition bd = this.beanDefinitionMap.get(beanName); if (bd == null) { - if (this.logger.isTraceEnabled()) { - this.logger.trace("No bean named '" + beanName + "' found in " + this); + if (logger.isTraceEnabled()) { + logger.trace("No bean named '" + beanName + "' found in " + this); } throw new NoSuchBeanDefinitionException(beanName); } @@ -741,8 +726,8 @@ protected boolean isBeanEligibleForMetadataCaching(String beanName) { @Override public void preInstantiateSingletons() throws BeansException { - if (this.logger.isDebugEnabled()) { - this.logger.debug("Pre-instantiating singletons in " + this); + if (logger.isDebugEnabled()) { + logger.debug("Pre-instantiating singletons in " + this); } // Iterate over a copy to allow for init methods which in turn register new bean definitions. @@ -821,34 +806,32 @@ public void registerBeanDefinition(String beanName, BeanDefinition beanDefinitio } } - BeanDefinition oldBeanDefinition; - - oldBeanDefinition = this.beanDefinitionMap.get(beanName); - if (oldBeanDefinition != null) { + BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName); + if (existingDefinition != null) { if (!isAllowBeanDefinitionOverriding()) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName + - "': There is already [" + oldBeanDefinition + "] bound."); + "': There is already [" + existingDefinition + "] bound."); } - else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) { + else if (existingDefinition.getRole() < beanDefinition.getRole()) { // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE - if (this.logger.isWarnEnabled()) { - this.logger.warn("Overriding user-defined bean definition for bean '" + beanName + + if (logger.isWarnEnabled()) { + logger.warn("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + - oldBeanDefinition + "] with [" + beanDefinition + "]"); + existingDefinition + "] with [" + beanDefinition + "]"); } } - else if (!beanDefinition.equals(oldBeanDefinition)) { - if (this.logger.isInfoEnabled()) { - this.logger.info("Overriding bean definition for bean '" + beanName + - "' with a different definition: replacing [" + oldBeanDefinition + + else if (!beanDefinition.equals(existingDefinition)) { + if (logger.isInfoEnabled()) { + logger.info("Overriding bean definition for bean '" + beanName + + "' with a different definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } else { - if (this.logger.isDebugEnabled()) { - this.logger.debug("Overriding bean definition for bean '" + beanName + - "' with an equivalent definition: replacing [" + oldBeanDefinition + + if (logger.isDebugEnabled()) { + logger.debug("Overriding bean definition for bean '" + beanName + + "' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } @@ -879,7 +862,7 @@ else if (!beanDefinition.equals(oldBeanDefinition)) { this.frozenBeanDefinitionNames = null; } - if (oldBeanDefinition != null || containsSingleton(beanName)) { + if (existingDefinition != null || containsSingleton(beanName)) { resetBeanDefinition(beanName); } } @@ -890,8 +873,8 @@ public void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionExc BeanDefinition bd = this.beanDefinitionMap.remove(beanName); if (bd == null) { - if (this.logger.isTraceEnabled()) { - this.logger.trace("No bean named '" + beanName + "' found in " + this); + if (logger.isTraceEnabled()) { + logger.trace("No bean named '" + beanName + "' found in " + this); } throw new NoSuchBeanDefinitionException(beanName); } @@ -999,24 +982,86 @@ private void clearByTypeCache() { //--------------------------------------------------------------------- @Override - public Object resolveDependency(DependencyDescriptor descriptor, String beanName, + public NamedBeanHolder resolveNamedBean(Class requiredType) throws BeansException { + NamedBeanHolder namedBean = resolveNamedBean(requiredType, (Object[]) null); + if (namedBean != null) { + return namedBean; + } + BeanFactory parent = getParentBeanFactory(); + if (parent instanceof AutowireCapableBeanFactory) { + return ((AutowireCapableBeanFactory) parent).resolveNamedBean(requiredType); + } + throw new NoSuchBeanDefinitionException(requiredType); + } + + @SuppressWarnings("unchecked") + private NamedBeanHolder resolveNamedBean(Class requiredType, Object... args) throws BeansException { + Assert.notNull(requiredType, "Required type must not be null"); + String[] candidateNames = getBeanNamesForType(requiredType); + + if (candidateNames.length > 1) { + List autowireCandidates = new ArrayList(candidateNames.length); + for (String beanName : candidateNames) { + if (!containsBeanDefinition(beanName) || getBeanDefinition(beanName).isAutowireCandidate()) { + autowireCandidates.add(beanName); + } + } + if (!autowireCandidates.isEmpty()) { + candidateNames = StringUtils.toStringArray(autowireCandidates); + } + } + + if (candidateNames.length == 1) { + String beanName = candidateNames[0]; + return new NamedBeanHolder(beanName, getBean(beanName, requiredType, args)); + } + else if (candidateNames.length > 1) { + Map candidates = new LinkedHashMap(candidateNames.length); + for (String beanName : candidateNames) { + if (containsSingleton(beanName)) { + candidates.put(beanName, getBean(beanName, requiredType, args)); + } + else { + candidates.put(beanName, getType(beanName)); + } + } + String candidateName = determinePrimaryCandidate(candidates, requiredType); + if (candidateName == null) { + candidateName = determineHighestPriorityCandidate(candidates, requiredType); + } + if (candidateName != null) { + Object beanInstance = candidates.get(candidateName); + if (beanInstance instanceof Class) { + beanInstance = getBean(candidateName, requiredType, args); + } + return new NamedBeanHolder(candidateName, (T) beanInstance); + } + throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet()); + } + + return null; + } + + @Override + public Object resolveDependency(DependencyDescriptor descriptor, String requestingBeanName, Set autowiredBeanNames, TypeConverter typeConverter) throws BeansException { descriptor.initParameterNameDiscovery(getParameterNameDiscoverer()); if (javaUtilOptionalClass == descriptor.getDependencyType()) { - return new OptionalDependencyFactory().createOptionalDependency(descriptor, beanName); + return new OptionalDependencyFactory().createOptionalDependency(descriptor, requestingBeanName); } else if (ObjectFactory.class == descriptor.getDependencyType() || ObjectProvider.class == descriptor.getDependencyType()) { - return new DependencyObjectProvider(descriptor, beanName); + return new DependencyObjectProvider(descriptor, requestingBeanName); } else if (javaxInjectProviderClass == descriptor.getDependencyType()) { - return new Jsr330ProviderFactory().createDependencyProvider(descriptor, beanName); + return new Jsr330ProviderFactory().createDependencyProvider(descriptor, requestingBeanName); } else { - Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, beanName); + Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary( + descriptor, requestingBeanName); if (result == null) { - result = doResolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter); + result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter); } return result; } @@ -1053,15 +1098,19 @@ public Object doResolveDependency(DependencyDescriptor descriptor, String beanNa Map matchingBeans = findAutowireCandidates(beanName, type, descriptor); if (matchingBeans.isEmpty()) { - if (descriptor.isRequired()) { - raiseNoMatchingBeanFound(type, descriptor.getResolvableType().toString(), descriptor); + if (isRequired(descriptor)) { + raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor); } return null; } + + String autowiredBeanName; + Object instanceCandidate; + if (matchingBeans.size() > 1) { - String primaryBeanName = determineAutowireCandidate(matchingBeans, descriptor); - if (primaryBeanName == null) { - if (descriptor.isRequired() || !indicatesMultipleBeans(type)) { + autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor); + if (autowiredBeanName == null) { + if (isRequired(descriptor) || !indicatesMultipleBeans(type)) { return descriptor.resolveNotUnique(type, matchingBeans); } else { @@ -1071,17 +1120,20 @@ public Object doResolveDependency(DependencyDescriptor descriptor, String beanNa return null; } } - if (autowiredBeanNames != null) { - autowiredBeanNames.add(primaryBeanName); - } - return matchingBeans.get(primaryBeanName); + instanceCandidate = matchingBeans.get(autowiredBeanName); + } + else { + // We have exactly one match. + Map.Entry entry = matchingBeans.entrySet().iterator().next(); + autowiredBeanName = entry.getKey(); + instanceCandidate = entry.getValue(); } - // We have exactly one match. - Map.Entry entry = matchingBeans.entrySet().iterator().next(); + if (autowiredBeanNames != null) { - autowiredBeanNames.add(entry.getKey()); + autowiredBeanNames.add(autowiredBeanName); } - return entry.getValue(); + return (instanceCandidate instanceof Class ? + descriptor.resolveCandidate(autowiredBeanName, type, this) : instanceCandidate); } finally { ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint); @@ -1094,9 +1146,17 @@ private Object resolveMultipleBeans(DependencyDescriptor descriptor, String bean Class type = descriptor.getDependencyType(); if (type.isArray()) { Class componentType = type.getComponentType(); - DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor); - targetDesc.increaseNestingLevel(); - Map matchingBeans = findAutowireCandidates(beanName, componentType, targetDesc); + ResolvableType resolvableType = descriptor.getResolvableType(); + Class resolvedArrayType = resolvableType.resolve(); + if (resolvedArrayType != null && resolvedArrayType != type) { + type = resolvedArrayType; + componentType = resolvableType.getComponentType().resolve(); + } + if (componentType == null) { + return null; + } + Map matchingBeans = findAutowireCandidates(beanName, componentType, + new MultiElementDescriptor(descriptor)); if (matchingBeans.isEmpty()) { return null; } @@ -1111,13 +1171,12 @@ private Object resolveMultipleBeans(DependencyDescriptor descriptor, String bean return result; } else if (Collection.class.isAssignableFrom(type) && type.isInterface()) { - Class elementType = descriptor.getCollectionType(); + Class elementType = descriptor.getResolvableType().asCollection().resolveGeneric(); if (elementType == null) { return null; } - DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor); - targetDesc.increaseNestingLevel(); - Map matchingBeans = findAutowireCandidates(beanName, elementType, targetDesc); + Map matchingBeans = findAutowireCandidates(beanName, elementType, + new MultiElementDescriptor(descriptor)); if (matchingBeans.isEmpty()) { return null; } @@ -1131,18 +1190,18 @@ else if (Collection.class.isAssignableFrom(type) && type.isInterface()) { } return result; } - else if (Map.class.isAssignableFrom(type) && type.isInterface()) { - Class keyType = descriptor.getMapKeyType(); + else if (Map.class == type) { + ResolvableType mapType = descriptor.getResolvableType().asMap(); + Class keyType = mapType.resolveGeneric(0); if (String.class != keyType) { return null; } - Class valueType = descriptor.getMapValueType(); + Class valueType = mapType.resolveGeneric(1); if (valueType == null) { return null; } - DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor); - targetDesc.increaseNestingLevel(); - Map matchingBeans = findAutowireCandidates(beanName, valueType, targetDesc); + Map matchingBeans = findAutowireCandidates(beanName, valueType, + new MultiElementDescriptor(descriptor)); if (matchingBeans.isEmpty()) { return null; } @@ -1156,6 +1215,13 @@ else if (Map.class.isAssignableFrom(type) && type.isInterface()) { } } + private boolean isRequired(DependencyDescriptor descriptor) { + AutowireCandidateResolver resolver = getAutowireCandidateResolver(); + return (resolver instanceof SimpleAutowireCandidateResolver ? + ((SimpleAutowireCandidateResolver) resolver).isRequired(descriptor) : + descriptor.isRequired()); + } + private boolean indicatesMultipleBeans(Class type) { return (type.isArray() || (type.isInterface() && (Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type)))); @@ -1172,7 +1238,7 @@ private Comparator adaptDependencyComparator(Map matchin } } - private FactoryAwareOrderSourceProvider createFactoryAwareOrderSourceProvider(Map beans) { + private OrderComparator.OrderSourceProvider createFactoryAwareOrderSourceProvider(Map beans) { IdentityHashMap instancesToBeanNames = new IdentityHashMap(); for (Map.Entry entry : beans.entrySet()) { instancesToBeanNames.put(entry.getValue(), entry.getKey()); @@ -1209,24 +1275,27 @@ protected Map findAutowireCandidates( } } } - for (String candidateName : candidateNames) { - if (!isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, descriptor)) { - result.put(candidateName, descriptor.resolveCandidate(candidateName, requiredType, this)); + for (String candidate : candidateNames) { + if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) { + addCandidateEntry(result, candidate, descriptor, requiredType); } } if (result.isEmpty() && !indicatesMultipleBeans(requiredType)) { // Consider fallback matches if the first pass failed to find anything... DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch(); - for (String candidateName : candidateNames) { - if (!isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, fallbackDescriptor)) { - result.put(candidateName, descriptor.resolveCandidate(candidateName, requiredType, this)); + for (String candidate : candidateNames) { + if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor)) { + addCandidateEntry(result, candidate, descriptor, requiredType); } } if (result.isEmpty()) { - // Consider self references before as a final pass - for (String candidateName : candidateNames) { - if (isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, fallbackDescriptor)) { - result.put(candidateName, descriptor.resolveCandidate(candidateName, requiredType, this)); + // Consider self references as a final pass... + // but in the case of a dependency collection, not the very same bean itself. + for (String candidate : candidateNames) { + if (isSelfReference(beanName, candidate) && + (!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) && + isAutowireCandidate(candidate, fallbackDescriptor)) { + addCandidateEntry(result, candidate, descriptor, requiredType); } } } @@ -1234,31 +1303,46 @@ protected Map findAutowireCandidates( return result; } + /** + * Add an entry to the candidate map: a bean instance if available or just the resolved + * type, preventing early bean initialization ahead of primary candidate selection. + */ + private void addCandidateEntry(Map candidates, String candidateName, + DependencyDescriptor descriptor, Class requiredType) { + + if (descriptor instanceof MultiElementDescriptor || containsSingleton(candidateName)) { + candidates.put(candidateName, descriptor.resolveCandidate(candidateName, requiredType, this)); + } + else { + candidates.put(candidateName, getType(candidateName)); + } + } + /** * Determine the autowire candidate in the given set of beans. *

Looks for {@code @Primary} and {@code @Priority} (in that order). - * @param candidateBeans a Map of candidate names and candidate instances + * @param candidates a Map of candidate names and candidate instances * that match the required type, as returned by {@link #findAutowireCandidates} * @param descriptor the target dependency to match against * @return the name of the autowire candidate, or {@code null} if none found */ - protected String determineAutowireCandidate(Map candidateBeans, DependencyDescriptor descriptor) { + protected String determineAutowireCandidate(Map candidates, DependencyDescriptor descriptor) { Class requiredType = descriptor.getDependencyType(); - String primaryCandidate = determinePrimaryCandidate(candidateBeans, requiredType); + String primaryCandidate = determinePrimaryCandidate(candidates, requiredType); if (primaryCandidate != null) { return primaryCandidate; } - String priorityCandidate = determineHighestPriorityCandidate(candidateBeans, requiredType); + String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType); if (priorityCandidate != null) { return priorityCandidate; } // Fallback - for (Map.Entry entry : candidateBeans.entrySet()) { - String candidateBeanName = entry.getKey(); + for (Map.Entry entry : candidates.entrySet()) { + String candidateName = entry.getKey(); Object beanInstance = entry.getValue(); if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) || - matchesBeanName(candidateBeanName, descriptor.getDependencyName())) { - return candidateBeanName; + matchesBeanName(candidateName, descriptor.getDependencyName())) { + return candidateName; } } return null; @@ -1266,15 +1350,15 @@ protected String determineAutowireCandidate(Map candidateBeans, /** * Determine the primary candidate in the given set of beans. - * @param candidateBeans a Map of candidate names and candidate instances - * that match the required type + * @param candidates a Map of candidate names and candidate instances + * (or candidate classes if not created yet) that match the required type * @param requiredType the target dependency type to match against * @return the name of the primary candidate, or {@code null} if none found * @see #isPrimary(String, Object) */ - protected String determinePrimaryCandidate(Map candidateBeans, Class requiredType) { + protected String determinePrimaryCandidate(Map candidates, Class requiredType) { String primaryBeanName = null; - for (Map.Entry entry : candidateBeans.entrySet()) { + for (Map.Entry entry : candidates.entrySet()) { String candidateBeanName = entry.getKey(); Object beanInstance = entry.getValue(); if (isPrimary(candidateBeanName, beanInstance)) { @@ -1282,8 +1366,8 @@ protected String determinePrimaryCandidate(Map candidateBeans, C boolean candidateLocal = containsBeanDefinition(candidateBeanName); boolean primaryLocal = containsBeanDefinition(primaryBeanName); if (candidateLocal && primaryLocal) { - throw new NoUniqueBeanDefinitionException(requiredType, candidateBeans.size(), - "more than one 'primary' bean found among candidates: " + candidateBeans.keySet()); + throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(), + "more than one 'primary' bean found among candidates: " + candidates.keySet()); } else if (candidateLocal) { primaryBeanName = candidateBeanName; @@ -1298,29 +1382,30 @@ else if (candidateLocal) { } /** - * Determine the candidate with the highest priority in the given set of beans. As - * defined by the {@link org.springframework.core.Ordered} interface, the lowest - * value has the highest priority. - * @param candidateBeans a Map of candidate names and candidate instances - * that match the required type + * Determine the candidate with the highest priority in the given set of beans. + *

Based on {@code @javax.annotation.Priority}. As defined by the related + * {@link org.springframework.core.Ordered} interface, the lowest value has + * the highest priority. + * @param candidates a Map of candidate names and candidate instances + * (or candidate classes if not created yet) that match the required type * @param requiredType the target dependency type to match against * @return the name of the candidate with the highest priority, * or {@code null} if none found * @see #getPriority(Object) */ - protected String determineHighestPriorityCandidate(Map candidateBeans, Class requiredType) { + protected String determineHighestPriorityCandidate(Map candidates, Class requiredType) { String highestPriorityBeanName = null; Integer highestPriority = null; - for (Map.Entry entry : candidateBeans.entrySet()) { + for (Map.Entry entry : candidates.entrySet()) { String candidateBeanName = entry.getKey(); Object beanInstance = entry.getValue(); Integer candidatePriority = getPriority(beanInstance); if (candidatePriority != null) { if (highestPriorityBeanName != null) { if (candidatePriority.equals(highestPriority)) { - throw new NoUniqueBeanDefinitionException(requiredType, candidateBeans.size(), - "Multiple beans found with the same priority ('" + highestPriority + "') " + - "among candidates: " + candidateBeans.keySet()); + throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(), + "Multiple beans found with the same priority ('" + highestPriority + + "') among candidates: " + candidates.keySet()); } else if (candidatePriority < highestPriority) { highestPriorityBeanName = candidateBeanName; @@ -1347,9 +1432,9 @@ protected boolean isPrimary(String beanName, Object beanInstance) { if (containsBeanDefinition(beanName)) { return getMergedLocalBeanDefinition(beanName).isPrimary(); } - BeanFactory parentFactory = getParentBeanFactory(); - return (parentFactory instanceof DefaultListableBeanFactory && - ((DefaultListableBeanFactory) parentFactory).isPrimary(beanName, beanInstance)); + BeanFactory parent = getParentBeanFactory(); + return (parent instanceof DefaultListableBeanFactory && + ((DefaultListableBeanFactory) parent).isPrimary(beanName, beanInstance)); } /** @@ -1397,12 +1482,12 @@ private boolean isSelfReference(String beanName, String candidateName) { * for an unresolvable dependency. */ private void raiseNoMatchingBeanFound( - Class type, String dependencyDescription, DependencyDescriptor descriptor) throws BeansException { + Class type, ResolvableType resolvableType, DependencyDescriptor descriptor) throws BeansException { checkBeanNotOfRequiredType(type, descriptor); - throw new NoSuchBeanDefinitionException(type, dependencyDescription, - "expected at least 1 bean which qualifies as autowire candidate for this dependency. " + + throw new NoSuchBeanDefinitionException(resolvableType, + "expected at least 1 bean which qualifies as autowire candidate. " + "Dependency annotations: " + ObjectUtils.nullSafeToString(descriptor.getAnnotations())); } @@ -1416,17 +1501,18 @@ private void checkBeanNotOfRequiredType(Class type, DependencyDescriptor desc Class targetType = mbd.getTargetType(); if (targetType != null && type.isAssignableFrom(targetType) && isAutowireCandidate(beanName, mbd, descriptor, getAutowireCandidateResolver())) { - // Probably a poxy interfering with target type match -> throw meaningful exception. + // Probably a proxy interfering with target type match -> throw meaningful exception. Object beanInstance = getSingleton(beanName, false); Class beanType = (beanInstance != null ? beanInstance.getClass() : predictBeanType(beanName, mbd)); - if (type != beanType) { + if (!type.isAssignableFrom((beanType))) { throw new BeanNotOfRequiredTypeException(beanName, type, beanType); } } } - if (getParentBeanFactory() instanceof DefaultListableBeanFactory) { - ((DefaultListableBeanFactory) getParentBeanFactory()).checkBeanNotOfRequiredType(type, descriptor); + BeanFactory parent = getParentBeanFactory(); + if (parent instanceof DefaultListableBeanFactory) { + ((DefaultListableBeanFactory) parent).checkBeanNotOfRequiredType(type, descriptor); } } @@ -1488,7 +1574,32 @@ private Object readResolve() { } } // Lenient fallback: dummy factory in case of original factory not found... - return new StaticListableBeanFactory(Collections. emptyMap()); + DefaultListableBeanFactory dummyFactory = new DefaultListableBeanFactory(); + dummyFactory.serializationId = this.id; + return dummyFactory; + } + } + + + /** + * A dependency descriptor marker for nested elements. + */ + private static class NestedDependencyDescriptor extends DependencyDescriptor { + + public NestedDependencyDescriptor(DependencyDescriptor original) { + super(original); + increaseNestingLevel(); + } + } + + + /** + * A dependency descriptor marker for multiple elements. + */ + private static class MultiElementDescriptor extends NestedDependencyDescriptor { + + public MultiElementDescriptor(DependencyDescriptor original) { + super(original); } } @@ -1500,7 +1611,7 @@ private Object readResolve() { private class OptionalDependencyFactory { public Object createOptionalDependency(DependencyDescriptor descriptor, String beanName, final Object... args) { - DependencyDescriptor descriptorToUse = new DependencyDescriptor(descriptor) { + DependencyDescriptor descriptorToUse = new NestedDependencyDescriptor(descriptor) { @Override public boolean isRequired() { return false; @@ -1511,7 +1622,6 @@ public Object resolveCandidate(String beanName, Class requiredType, BeanFacto super.resolveCandidate(beanName, requiredType, beanFactory)); } }; - descriptorToUse.increaseNestingLevel(); return Optional.ofNullable(doResolveDependency(descriptorToUse, beanName, null, null)); } } @@ -1529,9 +1639,8 @@ private class DependencyObjectProvider implements ObjectProvider, Serial private final String beanName; public DependencyObjectProvider(DependencyDescriptor descriptor, String beanName) { - this.descriptor = new DependencyDescriptor(descriptor); - this.descriptor.increaseNestingLevel(); - this.optional = this.descriptor.getDependencyType().equals(javaUtilOptionalClass); + this.descriptor = new NestedDependencyDescriptor(descriptor); + this.optional = (this.descriptor.getDependencyType() == javaUtilOptionalClass); this.beanName = beanName; } @@ -1551,7 +1660,7 @@ public Object getObject(final Object... args) throws BeansException { return new OptionalDependencyFactory().createOptionalDependency(this.descriptor, this.beanName, args); } else { - DependencyDescriptor descriptorToUse = new DependencyDescriptor(descriptor) { + DependencyDescriptor descriptorToUse = new DependencyDescriptor(this.descriptor) { @Override public Object resolveCandidate(String beanName, Class requiredType, BeanFactory beanFactory) { return ((AbstractBeanFactory) beanFactory).getBean(beanName, requiredType, args); @@ -1567,7 +1676,7 @@ public Object getIfAvailable() throws BeansException { return new OptionalDependencyFactory().createOptionalDependency(this.descriptor, this.beanName); } else { - DependencyDescriptor descriptorToUse = new DependencyDescriptor(descriptor) { + DependencyDescriptor descriptorToUse = new DependencyDescriptor(this.descriptor) { @Override public boolean isRequired() { return false; @@ -1579,7 +1688,7 @@ public boolean isRequired() { @Override public Object getIfUnique() throws BeansException { - DependencyDescriptor descriptorToUse = new DependencyDescriptor(descriptor) { + DependencyDescriptor descriptorToUse = new DependencyDescriptor(this.descriptor) { @Override public boolean isRequired() { return false; @@ -1600,7 +1709,7 @@ public Object resolveNotUnique(Class type, Map matchingBeans) /** - * Serializable ObjectFactory for lazy resolution of a dependency. + * A {@code javax.inject.Provider} implementation for lazy resolution of a dependency. */ private class Jsr330DependencyProvider extends DependencyObjectProvider implements Provider { @@ -1647,16 +1756,16 @@ public Object getOrderSource(Object obj) { if (beanDefinition == null) { return null; } - List sources = new ArrayList(); + List sources = new ArrayList(2); Method factoryMethod = beanDefinition.getResolvedFactoryMethod(); if (factoryMethod != null) { sources.add(factoryMethod); } Class targetType = beanDefinition.getTargetType(); - if (targetType != null && !targetType.equals(obj.getClass())) { + if (targetType != null && targetType != obj.getClass()) { sources.add(targetType); } - return sources.toArray(new Object[sources.size()]); + return sources.toArray(); } private RootBeanDefinition getRootBeanDefinition(String beanName) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java index d3d21347023f..c40dc7bac41e 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -214,7 +214,7 @@ public Object getSingleton(String beanName, ObjectFactory singletonFactory) { if (singletonObject == null) { if (this.singletonsCurrentlyInDestruction) { throw new BeanCreationNotAllowedException(beanName, - "Singleton bean creation not allowed while the singletons of this factory are in destruction " + + "Singleton bean creation not allowed while singletons of this factory are in destruction " + "(Do not request a bean from a BeanFactory in a destroy method implementation!)"); } if (logger.isDebugEnabled()) { @@ -386,15 +386,8 @@ public void registerDisposableBean(String beanName, DisposableBean bean) { * @see #registerDependentBean */ public void registerContainedBean(String containedBeanName, String containingBeanName) { - // A quick check for an existing entry upfront, avoiding synchronization... - Set containedBeans = this.containedBeanMap.get(containingBeanName); - if (containedBeans != null && containedBeans.contains(containedBeanName)) { - return; - } - - // No entry yet -> fully synchronized manipulation of the containedBeans Set synchronized (this.containedBeanMap) { - containedBeans = this.containedBeanMap.get(containingBeanName); + Set containedBeans = this.containedBeanMap.get(containingBeanName); if (containedBeans == null) { containedBeans = new LinkedHashSet(8); this.containedBeanMap.put(containingBeanName, containedBeans); @@ -411,16 +404,10 @@ public void registerContainedBean(String containedBeanName, String containingBea * @param dependentBeanName the name of the dependent bean */ public void registerDependentBean(String beanName, String dependentBeanName) { - // A quick check for an existing entry upfront, avoiding synchronization... String canonicalName = canonicalName(beanName); - Set dependentBeans = this.dependentBeanMap.get(canonicalName); - if (dependentBeans != null && dependentBeans.contains(dependentBeanName)) { - return; - } - // No entry yet -> fully synchronized manipulation of the dependentBeans Set synchronized (this.dependentBeanMap) { - dependentBeans = this.dependentBeanMap.get(canonicalName); + Set dependentBeans = this.dependentBeanMap.get(canonicalName); if (dependentBeans == null) { dependentBeans = new LinkedHashSet(8); this.dependentBeanMap.put(canonicalName, dependentBeans); @@ -445,7 +432,9 @@ public void registerDependentBean(String beanName, String dependentBeanName) { * @since 4.0 */ protected boolean isDependent(String beanName, String dependentBeanName) { - return isDependent(beanName, dependentBeanName, null); + synchronized (this.dependentBeanMap) { + return isDependent(beanName, dependentBeanName, null); + } } private boolean isDependent(String beanName, String dependentBeanName, Set alreadySeen) { @@ -490,7 +479,9 @@ public String[] getDependentBeans(String beanName) { if (dependentBeans == null) { return new String[0]; } - return StringUtils.toStringArray(dependentBeans); + synchronized (this.dependentBeanMap) { + return StringUtils.toStringArray(dependentBeans); + } } /** @@ -504,7 +495,9 @@ public String[] getDependenciesForBean(String beanName) { if (dependenciesForBean == null) { return new String[0]; } - return dependenciesForBean.toArray(new String[dependenciesForBean.size()]); + synchronized (this.dependenciesForBeanMap) { + return StringUtils.toStringArray(dependenciesForBean); + } } public void destroySingletons() { @@ -527,6 +520,14 @@ public void destroySingletons() { this.dependentBeanMap.clear(); this.dependenciesForBeanMap.clear(); + clearSingletonCache(); + } + + /** + * Clear all cached singleton instances in this registry. + * @since 4.3.15 + */ + protected void clearSingletonCache() { synchronized (this.singletonObjects) { this.singletonObjects.clear(); this.singletonFactories.clear(); @@ -562,7 +563,11 @@ public void destroySingleton(String beanName) { */ protected void destroyBean(String beanName, DisposableBean bean) { // Trigger destruction of dependent beans first... - Set dependencies = this.dependentBeanMap.remove(beanName); + Set dependencies; + synchronized (this.dependentBeanMap) { + // Within full synchronization in order to guarantee a disconnected Set + dependencies = this.dependentBeanMap.remove(beanName); + } if (dependencies != null) { if (logger.isDebugEnabled()) { logger.debug("Retrieved dependent beans for bean '" + beanName + "': " + dependencies); @@ -583,7 +588,11 @@ protected void destroyBean(String beanName, DisposableBean bean) { } // Trigger destruction of contained beans... - Set containedBeans = this.containedBeanMap.remove(beanName); + Set containedBeans; + synchronized (this.containedBeanMap) { + // Within full synchronization in order to guarantee a disconnected Set + containedBeans = this.containedBeanMap.remove(beanName); + } if (containedBeans != null) { for (String containedBeanName : containedBeans) { destroySingleton(containedBeanName); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java index 53dc17657e63..90671851cec9 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -109,6 +109,11 @@ protected Object getObjectFromFactoryBean(FactoryBean factory, String beanNam } else { if (object != null && shouldPostProcess) { + if (isSingletonCurrentlyInCreation(beanName)) { + // Temporarily return non-post-processed object, not storing it yet.. + return object; + } + beforeSingletonCreation(beanName); try { object = postProcessObjectFromFactoryBean(object, beanName); } @@ -116,8 +121,13 @@ protected Object getObjectFromFactoryBean(FactoryBean factory, String beanNam throw new BeanCreationException(beanName, "Post-processing of FactoryBean's singleton object failed", ex); } + finally { + afterSingletonCreation(beanName); + } + } + if (containsSingleton(beanName)) { + this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT)); } - this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT)); } } return (object != NULL_OBJECT ? object : null); @@ -218,12 +228,25 @@ protected FactoryBean getFactoryBean(String beanName, Object beanInstance) th */ @Override protected void removeSingleton(String beanName) { - super.removeSingleton(beanName); - this.factoryBeanObjectCache.remove(beanName); + synchronized (getSingletonMutex()) { + super.removeSingleton(beanName); + this.factoryBeanObjectCache.remove(beanName); + } + } + + /** + * Overridden to clear the FactoryBean object cache as well. + */ + @Override + protected void clearSingletonCache() { + synchronized (getSingletonMutex()) { + super.clearSingletonCache(); + this.factoryBeanObjectCache.clear(); + } } /** - * Returns the security context for this bean factory. If a security manager + * Return the security context for this bean factory. If a security manager * is set, interaction with the user code will be executed using the privileged * of the security context returned by this method. * @see AccessController#getContext() diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericBeanDefinition.java index 77c08f67390c..04ec986dec5f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericBeanDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,10 +45,7 @@ public class GenericBeanDefinition extends AbstractBeanDefinition { * Create a new GenericBeanDefinition, to be configured through its bean * properties and configuration methods. * @see #setBeanClass - * @see #setBeanClassName * @see #setScope - * @see #setAutowireMode - * @see #setDependencyCheck * @see #setConstructorArgumentValues * @see #setPropertyValues */ diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java index 65554ce9b57d..7ffbed3e0e5c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,8 @@ * @author Juergen Hoeller * @since 4.0 */ -public class GenericTypeAwareAutowireCandidateResolver implements AutowireCandidateResolver, BeanFactoryAware { +public class GenericTypeAwareAutowireCandidateResolver extends SimpleAutowireCandidateResolver + implements BeanFactoryAware { private BeanFactory beanFactory; @@ -57,8 +58,8 @@ protected final BeanFactory getBeanFactory() { @Override public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) { - if (!bdHolder.getBeanDefinition().isAutowireCandidate()) { - // if explicitly false, do not proceed with any other checks + if (!super.isAutowireCandidate(bdHolder, descriptor)) { + // If explicitly false, do not proceed with any other checks... return false; } return (descriptor == null || checkGenericTypeMatch(bdHolder, descriptor)); @@ -74,21 +75,31 @@ protected boolean checkGenericTypeMatch(BeanDefinitionHolder bdHolder, Dependenc // No generic type -> we know it's a Class type-match, so no need to check again. return true; } + ResolvableType targetType = null; + boolean cacheType = false; RootBeanDefinition rbd = null; if (bdHolder.getBeanDefinition() instanceof RootBeanDefinition) { rbd = (RootBeanDefinition) bdHolder.getBeanDefinition(); } if (rbd != null) { - // First, check factory method return type, if applicable - targetType = getReturnTypeForFactoryMethod(rbd, descriptor); + targetType = rbd.targetType; if (targetType == null) { - RootBeanDefinition dbd = getResolvedDecoratedDefinition(rbd); - if (dbd != null) { - targetType = getReturnTypeForFactoryMethod(dbd, descriptor); + cacheType = true; + // First, check factory method return type, if applicable + targetType = getReturnTypeForFactoryMethod(rbd, descriptor); + if (targetType == null) { + RootBeanDefinition dbd = getResolvedDecoratedDefinition(rbd); + if (dbd != null) { + targetType = dbd.targetType; + if (targetType == null) { + targetType = getReturnTypeForFactoryMethod(dbd, descriptor); + } + } } } } + if (targetType == null) { // Regular case: straight bean instance, with BeanFactory available. if (this.beanFactory != null) { @@ -106,7 +117,14 @@ protected boolean checkGenericTypeMatch(BeanDefinitionHolder bdHolder, Dependenc } } } - if (targetType == null || (descriptor.fallbackMatchAllowed() && targetType.hasUnresolvableGenerics())) { + + if (targetType == null) { + return true; + } + if (cacheType) { + rbd.targetType = targetType; + } + if (descriptor.fallbackMatchAllowed() && targetType.hasUnresolvableGenerics()) { return true; } // Full check for complex generic type match... @@ -130,40 +148,22 @@ protected RootBeanDefinition getResolvedDecoratedDefinition(RootBeanDefinition r protected ResolvableType getReturnTypeForFactoryMethod(RootBeanDefinition rbd, DependencyDescriptor descriptor) { // Should typically be set for any kind of factory method, since the BeanFactory // pre-resolves them before reaching out to the AutowireCandidateResolver... - Class preResolved = rbd.resolvedFactoryMethodReturnType; - if (preResolved != null) { - return ResolvableType.forClass(preResolved); + ResolvableType returnType = rbd.factoryMethodReturnType; + if (returnType == null) { + Method factoryMethod = rbd.getResolvedFactoryMethod(); + if (factoryMethod != null) { + returnType = ResolvableType.forMethodReturnType(factoryMethod); + } } - else { - Method resolvedFactoryMethod = rbd.getResolvedFactoryMethod(); - if (resolvedFactoryMethod != null) { - if (descriptor.getDependencyType().isAssignableFrom(resolvedFactoryMethod.getReturnType())) { - // Only use factory method metadata if the return type is actually expressive enough - // for our dependency. Otherwise, the returned instance type may have matched instead - // in case of a singleton instance having been registered with the container already. - return ResolvableType.forMethodReturnType(resolvedFactoryMethod); - } + if (returnType != null) { + Class resolvedClass = returnType.resolve(); + if (resolvedClass != null && descriptor.getDependencyType().isAssignableFrom(resolvedClass)) { + // Only use factory method metadata if the return type is actually expressive enough + // for our dependency. Otherwise, the returned instance type may have matched instead + // in case of a singleton instance having been registered with the container already. + return returnType; } - return null; } - } - - - /** - * This implementation always returns {@code null}, leaving suggested value support up - * to subclasses. - */ - @Override - public Object getSuggestedValue(DependencyDescriptor descriptor) { - return null; - } - - /** - * This implementation always returns {@code null}, leaving lazy resolution support up - * to subclasses. - */ - @Override - public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, String beanName) { return null; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/MergedBeanDefinitionPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/MergedBeanDefinitionPostProcessor.java index d9846442a9f8..02d050aeadc6 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/MergedBeanDefinitionPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/MergedBeanDefinitionPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,20 +20,20 @@ /** * Post-processor callback interface for merged bean definitions at runtime. - * {@link BeanPostProcessor} implementations may implement this sub-interface in - * order to post-process the merged bean definition that the Spring BeanFactory - * uses to create a specific bean instance. + * {@link BeanPostProcessor} implementations may implement this sub-interface in order + * to post-process the merged bean definition (a processed copy of the original bean + * definition) that the Spring {@code BeanFactory} uses to create a bean instance. * *

The {@link #postProcessMergedBeanDefinition} method may for example introspect * the bean definition in order to prepare some cached metadata before post-processing - * actual instances of a bean. It is also allowed to modify the bean definition - * but only for bean definition properties which are actually intended - * for concurrent modification. Basically, this only applies to operations - * defined on the {@link RootBeanDefinition} itself but not to the properties - * of its base classes. + * actual instances of a bean. It is also allowed to modify the bean definition but + * only for definition properties which are actually intended for concurrent + * modification. Essentially, this only applies to operations defined on the + * {@link RootBeanDefinition} itself but not to the properties of its base classes. * * @author Juergen Hoeller * @since 2.5 + * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#getMergedBeanDefinition */ public interface MergedBeanDefinitionPostProcessor extends BeanPostProcessor { 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 91ad5d8e96cf..93da06b72f38 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 @@ -16,6 +16,7 @@ package org.springframework.beans.factory.support; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.util.HashSet; @@ -25,6 +26,7 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.core.ResolvableType; import org.springframework.util.Assert; /** @@ -48,22 +50,28 @@ @SuppressWarnings("serial") public class RootBeanDefinition extends AbstractBeanDefinition { - boolean allowCaching = true; - private BeanDefinitionHolder decoratedDefinition; - private volatile Class targetType; + private AnnotatedElement qualifiedElement; + + boolean allowCaching = true; boolean isFactoryMethodUnique = false; + volatile ResolvableType targetType; + + /** Package-visible field for caching the determined Class of a given bean definition */ + volatile Class resolvedTargetType; + + /** Package-visible field for caching the return type of a generically typed factory method */ + volatile ResolvableType factoryMethodReturnType; + + /** Common lock for the four constructor fields below */ final Object constructorArgumentLock = new Object(); /** Package-visible field for caching the resolved constructor or factory method */ Object resolvedConstructorOrFactoryMethod; - /** Package-visible field for caching the return type of a generically typed factory method */ - volatile Class resolvedFactoryMethodReturnType; - /** Package-visible field that marks the constructor arguments as resolved */ boolean constructorArgumentsResolved = false; @@ -73,6 +81,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition { /** Package-visible field for caching partly prepared constructor arguments */ Object[] preparedConstructorArguments; + /** Common lock for the two post-processing fields below */ final Object postProcessingLock = new Object(); /** Package-visible field that indicates MergedBeanDefinitionPostProcessor having been applied */ @@ -92,10 +101,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition { * Create a new RootBeanDefinition, to be configured through its bean * properties and configuration methods. * @see #setBeanClass - * @see #setBeanClassName * @see #setScope - * @see #setAutowireMode - * @see #setDependencyCheck * @see #setConstructorArgumentValues * @see #setPropertyValues */ @@ -106,6 +112,7 @@ public RootBeanDefinition() { /** * Create a new RootBeanDefinition for a singleton. * @param beanClass the class of the bean to instantiate + * @see #setBeanClass */ public RootBeanDefinition(Class beanClass) { super(); @@ -171,10 +178,11 @@ public RootBeanDefinition(String beanClassName, ConstructorArgumentValues cargs, */ public RootBeanDefinition(RootBeanDefinition original) { super(original); - this.allowCaching = original.allowCaching; this.decoratedDefinition = original.decoratedDefinition; - this.targetType = original.targetType; + this.qualifiedElement = original.qualifiedElement; + this.allowCaching = original.allowCaching; this.isFactoryMethodUnique = original.isFactoryMethodUnique; + this.targetType = original.targetType; } /** @@ -213,19 +221,52 @@ public BeanDefinitionHolder getDecoratedDefinition() { return this.decoratedDefinition; } + /** + * Specify the {@link AnnotatedElement} defining qualifiers, + * to be used instead of the target class or factory method. + * @since 4.3.3 + * @see #setTargetType(ResolvableType) + * @see #getResolvedFactoryMethod() + */ + public void setQualifiedElement(AnnotatedElement qualifiedElement) { + this.qualifiedElement = qualifiedElement; + } + + /** + * Return the {@link AnnotatedElement} defining qualifiers, if any. + * Otherwise, the factory method and target class will be checked. + * @since 4.3.3 + */ + public AnnotatedElement getQualifiedElement() { + return this.qualifiedElement; + } + + /** + * Specify a generics-containing target type of this bean definition, if known in advance. + * @since 4.3.3 + */ + public void setTargetType(ResolvableType targetType) { + this.targetType = targetType; + } + /** * Specify the target type of this bean definition, if known in advance. + * @since 3.2.2 */ public void setTargetType(Class targetType) { - this.targetType = targetType; + this.targetType = (targetType != null ? ResolvableType.forClass(targetType) : null); } /** * Return the target type of this bean definition, if known * (either specified in advance or resolved on first instantiation). + * @since 3.2.2 */ public Class getTargetType() { - return this.targetType; + if (this.resolvedTargetType != null) { + return this.resolvedTargetType; + } + return (this.targetType != null ? this.targetType.resolve() : null); } /** diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java index 4d29b95b6e2b..803810d834a2 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.springframework.beans.factory.support; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.DependencyDescriptor; @@ -27,20 +26,27 @@ * @author Mark Fisher * @author Juergen Hoeller * @since 2.5 - * @see BeanDefinition#isAutowireCandidate() */ public class SimpleAutowireCandidateResolver implements AutowireCandidateResolver { - /** - * Determine if the provided bean definition is an autowire candidate. - *

To be considered a candidate the bean's autowire-candidate - * attribute must not have been set to 'false'. - */ @Override public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) { return bdHolder.getBeanDefinition().isAutowireCandidate(); } + /** + * Determine whether the given descriptor is effectively required. + *

The default implementation checks {@link DependencyDescriptor#isRequired()}. + * @param descriptor the descriptor for the target method parameter or field + * @return whether the descriptor is marked as required or possibly indicating + * non-required status some other way (e.g. through a parameter annotation) + * @since 4.3.9 + * @see DependencyDescriptor#isRequired() + */ + public boolean isRequired(DependencyDescriptor descriptor) { + return descriptor.isRequired(); + } + @Override public Object getSuggestedValue(DependencyDescriptor descriptor) { return null; 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 32866846deec..f5aee86bcfb7 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-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -134,12 +135,21 @@ public Object getBean(String name) throws BeansException { @SuppressWarnings("unchecked") public T getBean(String name, Class requiredType) throws BeansException { Object bean = getBean(name); - if (requiredType != null && !requiredType.isAssignableFrom(bean.getClass())) { + if (requiredType != null && !requiredType.isInstance(bean)) { throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } return (T) bean; } + @Override + public Object getBean(String name, Object... args) throws BeansException { + if (!ObjectUtils.isEmpty(args)) { + throw new UnsupportedOperationException( + "StaticListableBeanFactory does not support explicit bean creation arguments"); + } + return getBean(name); + } + @Override public T getBean(Class requiredType) throws BeansException { String[] beanNames = getBeanNamesForType(requiredType); @@ -154,18 +164,9 @@ else if (beanNames.length > 1) { } } - @Override - public Object getBean(String name, Object... args) throws BeansException { - if (args != null) { - throw new UnsupportedOperationException( - "StaticListableBeanFactory does not support explicit bean creation arguments"); - } - return getBean(name); - } - @Override public T getBean(Class requiredType, Object... args) throws BeansException { - if (args != null) { + if (!ObjectUtils.isEmpty(args)) { throw new UnsupportedOperationException( "StaticListableBeanFactory does not support explicit bean creation arguments"); } @@ -248,7 +249,13 @@ public String[] getBeanDefinitionNames() { @Override public String[] getBeanNamesForType(ResolvableType type) { - boolean isFactoryType = (type != null && FactoryBean.class.isAssignableFrom(type.getRawClass())); + boolean isFactoryType = false; + if (type != null) { + Class resolved = type.resolve(); + if (resolved != null && FactoryBean.class.isAssignableFrom(resolved)) { + isFactoryType = true; + } + } List matches = new ArrayList(); for (Map.Entry entry : this.beans.entrySet()) { String name = entry.getKey(); @@ -326,7 +333,7 @@ public String[] getBeanNamesForAnnotation(Class annotation results.add(beanName); } } - return results.toArray(new String[results.size()]); + return StringUtils.toStringArray(results); } @Override @@ -346,7 +353,8 @@ public Map getBeansWithAnnotation(Class an public A findAnnotationOnBean(String beanName, Class annotationType) throws NoSuchBeanDefinitionException{ - return AnnotationUtils.findAnnotation(getType(beanName), annotationType); + Class beanType = getType(beanName); + return (beanType != null ? AnnotationUtils.findAnnotation(beanType, annotationType) : null); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/AbstractBeanDefinitionParser.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/AbstractBeanDefinitionParser.java index ad6c6e2f2edd..fc2b3280cc96 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/AbstractBeanDefinitionParser.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/AbstractBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -140,7 +140,7 @@ protected void registerBeanDefinition(BeanDefinitionHolder definition, BeanDefin /** * Central template method to actually parse the supplied {@link Element} * into one or more {@link BeanDefinition BeanDefinitions}. - * @param element the element that is to be parsed into one or more {@link BeanDefinition BeanDefinitions} + * @param element the element that is to be parsed into one or more {@link BeanDefinition BeanDefinitions} * @param parserContext the object encapsulating the current state of the parsing process; * provides access to a {@link org.springframework.beans.factory.support.BeanDefinitionRegistry} * @return the primary {@link BeanDefinition} resulting from the parsing of the supplied {@link Element} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParserDelegate.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParserDelegate.java index 8d3ddf19082d..64d429c462ea 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParserDelegate.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParserDelegate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -389,8 +389,7 @@ else if (parentDefaults != null) { } /** - * Return the defaults definition object, or {@code null} if the - * defaults have been initialized yet. + * Return the defaults definition object. */ public DocumentDefaultsDefinition getDefaults() { return this.defaults; @@ -403,8 +402,8 @@ public DocumentDefaultsDefinition getDefaults() { public BeanDefinitionDefaults getBeanDefinitionDefaults() { BeanDefinitionDefaults bdd = new BeanDefinitionDefaults(); bdd.setLazyInit("TRUE".equalsIgnoreCase(this.defaults.getLazyInit())); - bdd.setDependencyCheck(this.getDependencyCheck(DEFAULT_VALUE)); - bdd.setAutowireMode(this.getAutowireMode(DEFAULT_VALUE)); + bdd.setDependencyCheck(getDependencyCheck(DEFAULT_VALUE)); + bdd.setAutowireMode(getAutowireMode(DEFAULT_VALUE)); bdd.setInitMethodName(this.defaults.getInitMethod()); bdd.setDestroyMethodName(this.defaults.getDestroyMethod()); return bdd; @@ -1256,7 +1255,7 @@ else if (valueEle != null) { boolean hasKeyAttribute = entryEle.hasAttribute(KEY_ATTRIBUTE); boolean hasKeyRefAttribute = entryEle.hasAttribute(KEY_REF_ATTRIBUTE); if ((hasKeyAttribute && hasKeyRefAttribute) || - ((hasKeyAttribute || hasKeyRefAttribute)) && keyEle != null) { + (hasKeyAttribute || hasKeyRefAttribute) && keyEle != null) { error(" element is only allowed to contain either " + "a 'key' attribute OR a 'key-ref' attribute OR a sub-element", entryEle); } @@ -1285,7 +1284,7 @@ else if (keyEle != null) { boolean hasValueRefAttribute = entryEle.hasAttribute(VALUE_REF_ATTRIBUTE); boolean hasValueTypeAttribute = entryEle.hasAttribute(VALUE_TYPE_ATTRIBUTE); if ((hasValueAttribute && hasValueRefAttribute) || - ((hasValueAttribute || hasValueRefAttribute)) && valueEle != null) { + (hasValueAttribute || hasValueRefAttribute) && valueEle != null) { error(" element is only allowed to contain either " + "'value' attribute OR 'value-ref' attribute OR sub-element", entryEle); } @@ -1478,8 +1477,10 @@ private BeanDefinitionHolder parseNestedCustomElement(Element ele, BeanDefinitio /** - * Get the namespace URI for the supplied node. The default implementation uses {@link Node#getNamespaceURI}. - * Subclasses may override the default implementation to provide a different namespace identification mechanism. + * Get the namespace URI for the supplied node. + *

The default implementation uses {@link Node#getNamespaceURI}. + * Subclasses may override the default implementation to provide a + * different namespace identification mechanism. * @param node the node */ public String getNamespaceURI(Node node) { @@ -1487,8 +1488,10 @@ public String getNamespaceURI(Node node) { } /** - * Ges the local name for the supplied {@link Node}. The default implementation calls {@link Node#getLocalName}. - * Subclasses may override the default implementation to provide a different mechanism for getting the local name. + * Get the local name for the supplied {@link Node}. + *

The default implementation calls {@link Node#getLocalName}. + * Subclasses may override the default implementation to provide a + * different mechanism for getting the local name. * @param node the {@code Node} */ public String getLocalName(Node node) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeansDtdResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeansDtdResolver.java index ad096bff7b8b..fe971fdca9a7 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeansDtdResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeansDtdResolver.java @@ -58,7 +58,7 @@ public InputSource resolveEntity(String publicId, String systemId) throws IOExce "] and system ID [" + systemId + "]"); } if (systemId != null && systemId.endsWith(DTD_EXTENSION)) { - int lastPathSeparator = systemId.lastIndexOf("/"); + int lastPathSeparator = systemId.lastIndexOf('/'); int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator); if (dtdNameStart != -1) { String dtdFile = DTD_FILENAME + DTD_EXTENSION; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultNamespaceHandlerResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultNamespaceHandlerResolver.java index fd7d64cc8cdd..e839303f513f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultNamespaceHandlerResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultNamespaceHandlerResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -147,16 +147,21 @@ else if (handlerOrClassName instanceof NamespaceHandler) { * Load the specified NamespaceHandler mappings lazily. */ private Map getHandlerMappings() { - if (this.handlerMappings == null) { + Map handlerMappings = this.handlerMappings; + if (handlerMappings == null) { synchronized (this) { - if (this.handlerMappings == null) { + handlerMappings = this.handlerMappings; + if (handlerMappings == null) { + if (logger.isDebugEnabled()) { + logger.debug("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]"); + } try { Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader); if (logger.isDebugEnabled()) { logger.debug("Loaded NamespaceHandler mappings: " + mappings); } - Map handlerMappings = new ConcurrentHashMap(mappings.size()); + handlerMappings = new ConcurrentHashMap(mappings.size()); CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings); this.handlerMappings = handlerMappings; } @@ -167,7 +172,7 @@ private Map getHandlerMappings() { } } } - return this.handlerMappings; + return handlerMappings; } 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 c36314c0d14b..2c780cd4ae42 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-2009 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,7 +44,7 @@ public final class ParserContext { private BeanDefinition containingBeanDefinition; - private final Stack containingComponents = new Stack(); + private final Stack containingComponents = new Stack(); public ParserContext(XmlReaderContext readerContext, BeanDefinitionParserDelegate delegate) { @@ -90,8 +90,7 @@ public Object extractSource(Object sourceCandidate) { } public CompositeComponentDefinition getContainingComponent() { - return (!this.containingComponents.isEmpty() ? - (CompositeComponentDefinition) this.containingComponents.lastElement() : null); + return (!this.containingComponents.isEmpty() ? this.containingComponents.lastElement() : null); } public void pushContainingComponent(CompositeComponentDefinition containingComponent) { @@ -99,7 +98,7 @@ public void pushContainingComponent(CompositeComponentDefinition containingCompo } public CompositeComponentDefinition popContainingComponent() { - return (CompositeComponentDefinition) this.containingComponents.pop(); + return this.containingComponents.pop(); } public void popAndRegisterContainingComponent() { 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 6cfefe7559b6..4798f0a7a7b3 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -122,7 +122,7 @@ public InputSource resolveEntity(String publicId, String systemId) throws IOExce } catch (FileNotFoundException ex) { if (logger.isDebugEnabled()) { - logger.debug("Couldn't find XML schema [" + systemId + "]: " + resource, ex); + logger.debug("Could not find XML schema [" + systemId + "]: " + resource, ex); } } } @@ -134,9 +134,11 @@ public InputSource resolveEntity(String publicId, String systemId) throws IOExce * Load the specified schema mappings lazily. */ private Map getSchemaMappings() { - if (this.schemaMappings == null) { + Map schemaMappings = this.schemaMappings; + if (schemaMappings == null) { synchronized (this) { - if (this.schemaMappings == null) { + schemaMappings = this.schemaMappings; + if (schemaMappings == null) { if (logger.isDebugEnabled()) { logger.debug("Loading schema mappings from [" + this.schemaMappingsLocation + "]"); } @@ -146,7 +148,7 @@ private Map getSchemaMappings() { if (logger.isDebugEnabled()) { logger.debug("Loaded schema mappings: " + mappings); } - Map schemaMappings = new ConcurrentHashMap(mappings.size()); + schemaMappings = new ConcurrentHashMap(mappings.size()); CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings); this.schemaMappings = schemaMappings; } @@ -157,7 +159,7 @@ private Map getSchemaMappings() { } } } - return this.schemaMappings; + return schemaMappings; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/UtilNamespaceHandler.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/UtilNamespaceHandler.java index c9e7d8b2ea43..6736e0df138f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/UtilNamespaceHandler.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/UtilNamespaceHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -88,7 +88,7 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit parserContext.getReaderContext().error("Attribute 'path' must not be empty", element); return; } - int dotIndex = path.indexOf("."); + int dotIndex = path.indexOf('.'); if (dotIndex == -1) { parserContext.getReaderContext().error( "Attribute 'path' must follow pattern 'beanName.propertyName'", element); @@ -200,6 +200,7 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit String location = element.getAttribute("location"); if (StringUtils.hasLength(location)) { + location = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(location); String[] locations = StringUtils.commaDelimitedListToStringArray(location); builder.addPropertyValue("locations", locations); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java index 0f7091ade4bc..e2eaa57c4a87 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -314,7 +314,7 @@ public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreExce public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isInfoEnabled()) { - logger.info("Loading XML bean definitions from " + encodedResource.getResource()); + logger.info("Loading XML bean definitions from " + encodedResource); } Set currentResources = this.resourcesCurrentlyBeingLoaded.get(); @@ -430,13 +430,13 @@ protected Document doLoadDocument(InputSource inputSource, Resource resource) th getValidationModeForResource(resource), isNamespaceAware()); } - /** - * Gets the validation mode for the specified {@link Resource}. If no explicit - * validation mode has been configured then the validation mode is - * {@link #detectValidationMode detected}. + * Determine the validation mode for the specified {@link Resource}. + * If no explicit validation mode has been configured, then the validation + * mode gets {@link #detectValidationMode detected} from the given resource. *

Override this method if you would like full control over the validation * mode, even when something other than {@link #VALIDATION_AUTO} was set. + * @see #detectValidationMode */ protected int getValidationModeForResource(Resource resource) { int validationModeToUse = getValidationMode(); @@ -454,7 +454,7 @@ protected int getValidationModeForResource(Resource resource) { } /** - * Detects which kind of validation to perform on the XML file identified + * Detect which kind of validation to perform on the XML file identified * by the supplied {@link Resource}. If the file has a {@code DOCTYPE} * definition then DTD validation is used otherwise XSD validation is assumed. *

Override this method if you would like to customize resolution @@ -540,7 +540,8 @@ public NamespaceHandlerResolver getNamespaceHandlerResolver() { /** * Create the default implementation of {@link NamespaceHandlerResolver} used if none is specified. - * Default implementation returns an instance of {@link DefaultNamespaceHandlerResolver}. + *

The default implementation returns an instance of {@link DefaultNamespaceHandlerResolver}. + * @see DefaultNamespaceHandlerResolver#DefaultNamespaceHandlerResolver(ClassLoader) */ protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() { return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader()); diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/FileEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/FileEditor.java index 676fd0c6434d..fed7d430b3b3 100644 --- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/FileEditor.java +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/FileEditor.java @@ -84,8 +84,9 @@ public void setAsText(String text) throws IllegalArgumentException { // Check whether we got an absolute file path without "file:" prefix. // For backwards compatibility, we'll consider those as straight file path. + File file = null; if (!ResourceUtils.isUrl(text)) { - File file = new File(text); + file = new File(text); if (file.isAbsolute()) { setValue(file); return; @@ -97,7 +98,7 @@ public void setAsText(String text) throws IllegalArgumentException { Resource resource = (Resource) this.resourceEditor.getValue(); // If it's a URL or a path pointing to an existing resource, use it as-is. - if (ResourceUtils.isUrl(text) || resource.exists()) { + if (file == null || resource.exists()) { try { setValue(resource.getFile()); } @@ -107,8 +108,8 @@ public void setAsText(String text) throws IllegalArgumentException { } } else { - // Create a relative File reference and hope for the best. - setValue(new File(text)); + // Set a relative File reference and hope for the best. + setValue(file); } } 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 4767b1985486..f7c97dcc398c 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 @@ -37,13 +37,10 @@ *

Based on {@link Paths#get(URI)}'s resolution algorithm, checking * registered NIO file system providers, including the default file system * for "file:..." paths. Also supports Spring-style URL notation: any fully - * qualified standard URL and Spring's special "classpath:" pseudo-URL, - * as well as Spring's context-specific relative file paths. - * - *

Note that, in contrast to {@link FileEditor}, relative paths are only - * supported by Spring's resource abstraction here. Direct {@code Paths.get} - * resolution in a file system always has to go through the corresponding - * file system provider's scheme, i.e. "file" for the default file system. + * qualified standard URL and Spring's special "classpath:" pseudo-URL, as + * well as Spring's context-specific relative file paths. As a fallback, a + * path will be resolved in the file system via {@code Paths#get(String)} + * if no existing context-relative resource could be found. * * @author Juergen Hoeller * @since 4.3.2 @@ -79,10 +76,12 @@ public PathEditor(ResourceEditor resourceEditor) { @Override public void setAsText(String text) throws IllegalArgumentException { - if (!text.startsWith("/") && !text.startsWith(ResourceLoader.CLASSPATH_URL_PREFIX)) { + boolean nioPathCandidate = !text.startsWith(ResourceLoader.CLASSPATH_URL_PREFIX); + if (nioPathCandidate && !text.startsWith("/")) { try { URI uri = new URI(text); if (uri.getScheme() != null) { + nioPathCandidate = false; // Let's try NIO file system providers via Paths.get(URI) setValue(Paths.get(uri).normalize()); return; @@ -99,11 +98,19 @@ public void setAsText(String text) throws IllegalArgumentException { this.resourceEditor.setAsText(text); Resource resource = (Resource) this.resourceEditor.getValue(); - try { - setValue(resource != null ? resource.getFile().toPath() : null); + if (resource == null) { + setValue(null); + } + else if (!resource.exists() && nioPathCandidate) { + setValue(Paths.get(text).normalize()); } - catch (IOException ex) { - throw new IllegalArgumentException("Failed to retrieve file for " + resource, ex); + else { + try { + setValue(resource.getFile().toPath()); + } + catch (IOException ex) { + throw new IllegalArgumentException("Failed to retrieve file for " + resource, ex); + } } } diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/ResourceBundleEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/ResourceBundleEditor.java index 7eabc102b7a9..5ec38eb7dcb5 100644 --- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/ResourceBundleEditor.java +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/ResourceBundleEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -96,8 +96,7 @@ public void setAsText(String text) throws IllegalArgumentException { } String localeString = name.substring(separator + 1); Locale locale = StringUtils.parseLocaleString(localeString); - setValue((StringUtils.hasText(localeString)) ? ResourceBundle.getBundle(baseName, locale) : - ResourceBundle.getBundle(baseName)); + setValue(locale != null ? ResourceBundle.getBundle(baseName, locale) : ResourceBundle.getBundle(baseName)); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URIEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URIEditor.java index 7dc173a0d260..dece70f1e90a 100644 --- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URIEditor.java +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URIEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,6 +67,7 @@ public URIEditor() { * Create a new URIEditor, converting "classpath:" locations into * standard URIs (not trying to resolve them into physical resources). * @param encode indicates whether Strings will be encoded or not + * @since 3.0 */ public URIEditor(boolean encode) { this.classLoader = null; @@ -89,6 +90,7 @@ public URIEditor(ClassLoader classLoader) { * @param classLoader the ClassLoader to use for resolving "classpath:" locations * (may be {@code null} to indicate the default ClassLoader) * @param encode indicates whether Strings will be encoded or not + * @since 3.0 */ public URIEditor(ClassLoader classLoader, boolean encode) { this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader()); @@ -101,18 +103,14 @@ public void setAsText(String text) throws IllegalArgumentException { if (StringUtils.hasText(text)) { String uri = text.trim(); if (this.classLoader != null && uri.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX)) { - ClassPathResource resource = - new ClassPathResource(uri.substring(ResourceUtils.CLASSPATH_URL_PREFIX.length()), this.classLoader); + ClassPathResource resource = new ClassPathResource( + uri.substring(ResourceUtils.CLASSPATH_URL_PREFIX.length()), this.classLoader); try { - String url = resource.getURL().toString(); - setValue(createURI(url)); + setValue(resource.getURI()); } catch (IOException ex) { throw new IllegalArgumentException("Could not retrieve URI for " + resource + ": " + ex.getMessage()); } - catch (URISyntaxException ex) { - throw new IllegalArgumentException("Invalid URI syntax: " + ex); - } } else { try { @@ -129,9 +127,8 @@ public void setAsText(String text) throws IllegalArgumentException { } /** - * Create a URI instance for the given (resolved) String value. - *

The default implementation encodes the value into a RFC - * 2396 compliant URI. + * Create a URI instance for the given user-specified String value. + *

The default implementation encodes the value into a RFC-2396 compliant URI. * @param value the value to convert into a URI instance * @return the URI instance * @throws java.net.URISyntaxException if URI conversion failed diff --git a/spring-beans/src/main/java/org/springframework/beans/support/PagedListHolder.java b/spring-beans/src/main/java/org/springframework/beans/support/PagedListHolder.java index 4d7ff9601358..190ec8e17dc6 100644 --- a/spring-beans/src/main/java/org/springframework/beans/support/PagedListHolder.java +++ b/spring-beans/src/main/java/org/springframework/beans/support/PagedListHolder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ * PagedListHolder is a simple state holder for handling lists of objects, * separating them into pages. Page numbering starts with 0. * - *

This is mainly targetted at usage in web UIs. Typically, an instance will be + *

This is mainly targeted at usage in web UIs. Typically, an instance will be * instantiated with a list of beans, put into the session, and exported as model. * The properties can all be set/get programmatically, but the most common way will * be data binding, i.e. populating the bean from request parameters. The getters @@ -50,8 +50,14 @@ @SuppressWarnings("serial") public class PagedListHolder implements Serializable { + /** + * The default page size. + */ public static final int DEFAULT_PAGE_SIZE = 10; + /** + * The default maximum number of page links. + */ public static final int DEFAULT_MAX_LINKED_PAGES = 10; 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 246421c634cb..ce421c5a7467 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-2013 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,7 @@ * @author Chris Beams * @since 19.05.2003 */ -public final class BeanUtilsTests { +public class BeanUtilsTests { @Test public void testInstantiateClass() { @@ -193,7 +193,7 @@ public void testCopyPropertiesWithInvalidProperty() { source.setFlag2(true); InvalidProperty target = new InvalidProperty(); BeanUtils.copyProperties(source, target); - assertEquals(target.getName(), "name"); + assertEquals("name", target.getName()); assertTrue(target.getFlag1()); assertTrue(target.getFlag2()); } @@ -226,14 +226,14 @@ public void testResolveInvalidSignature() throws Exception { @Test public void testResolveWithAndWithoutArgList() throws Exception { - Method desiredMethod = MethodSignatureBean.class.getMethod("doSomethingElse", new Class[]{String.class, int.class}); + Method desiredMethod = MethodSignatureBean.class.getMethod("doSomethingElse", String.class, int.class); assertSignatureEquals(desiredMethod, "doSomethingElse"); assertNull(BeanUtils.resolveSignature("doSomethingElse()", MethodSignatureBean.class)); } @Test public void testResolveTypedSignature() throws Exception { - Method desiredMethod = MethodSignatureBean.class.getMethod("doSomethingElse", new Class[]{String.class, int.class}); + Method desiredMethod = MethodSignatureBean.class.getMethod("doSomethingElse", String.class, int.class); assertSignatureEquals(desiredMethod, "doSomethingElse(java.lang.String, int)"); } @@ -244,20 +244,20 @@ public void testResolveOverloadedSignature() throws Exception { assertSignatureEquals(desiredMethod, "overloaded()"); // resolve with single arg - desiredMethod = MethodSignatureBean.class.getMethod("overloaded", new Class[]{String.class}); + desiredMethod = MethodSignatureBean.class.getMethod("overloaded", String.class); assertSignatureEquals(desiredMethod, "overloaded(java.lang.String)"); // resolve with two args - desiredMethod = MethodSignatureBean.class.getMethod("overloaded", new Class[]{String.class, BeanFactory.class}); + desiredMethod = MethodSignatureBean.class.getMethod("overloaded", String.class, BeanFactory.class); assertSignatureEquals(desiredMethod, "overloaded(java.lang.String, org.springframework.beans.factory.BeanFactory)"); } @Test public void testResolveSignatureWithArray() throws Exception { - Method desiredMethod = MethodSignatureBean.class.getMethod("doSomethingWithAnArray", new Class[]{String[].class}); + Method desiredMethod = MethodSignatureBean.class.getMethod("doSomethingWithAnArray", String[].class); assertSignatureEquals(desiredMethod, "doSomethingWithAnArray(java.lang.String[])"); - desiredMethod = MethodSignatureBean.class.getMethod("doSomethingWithAMultiDimensionalArray", new Class[]{String[][].class}); + desiredMethod = MethodSignatureBean.class.getMethod("doSomethingWithAMultiDimensionalArray", String[][].class); assertSignatureEquals(desiredMethod, "doSomethingWithAMultiDimensionalArray(java.lang.String[][])"); } @@ -444,5 +444,18 @@ public void setValue(String aValue) { value = aValue; } } + + private static class BeanWithSingleNonDefaultConstructor { + + private final String name; + + public BeanWithSingleNonDefaultConstructor(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } } 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 f97ec8f7497c..f5c87cdf9db7 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-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,11 +31,11 @@ * @author Juergen Hoeller * @author Chris Beams */ -public final class BeanWrapperEnumTests { +public class BeanWrapperEnumTests { @Test public void testCustomEnum() { - GenericBean gb = new GenericBean(); + GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("customEnum", "VALUE_1"); assertEquals(CustomEnum.VALUE_1, gb.getCustomEnum()); @@ -43,7 +43,7 @@ public void testCustomEnum() { @Test public void testCustomEnumWithNull() { - GenericBean gb = new GenericBean(); + GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("customEnum", null); assertEquals(null, gb.getCustomEnum()); @@ -51,7 +51,7 @@ public void testCustomEnumWithNull() { @Test public void testCustomEnumWithEmptyString() { - GenericBean gb = new GenericBean(); + GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("customEnum", ""); assertEquals(null, gb.getCustomEnum()); @@ -59,7 +59,7 @@ public void testCustomEnumWithEmptyString() { @Test public void testCustomEnumArrayWithSingleValue() { - GenericBean gb = new GenericBean(); + GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("customEnumArray", "VALUE_1"); assertEquals(1, gb.getCustomEnumArray().length); @@ -68,7 +68,7 @@ public void testCustomEnumArrayWithSingleValue() { @Test public void testCustomEnumArrayWithMultipleValues() { - GenericBean gb = new GenericBean(); + GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("customEnumArray", new String[] {"VALUE_1", "VALUE_2"}); assertEquals(2, gb.getCustomEnumArray().length); @@ -78,7 +78,7 @@ public void testCustomEnumArrayWithMultipleValues() { @Test public void testCustomEnumArrayWithMultipleValuesAsCsv() { - GenericBean gb = new GenericBean(); + GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("customEnumArray", "VALUE_1,VALUE_2"); assertEquals(2, gb.getCustomEnumArray().length); @@ -88,7 +88,7 @@ public void testCustomEnumArrayWithMultipleValuesAsCsv() { @Test public void testCustomEnumSetWithSingleValue() { - GenericBean gb = new GenericBean(); + GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("customEnumSet", "VALUE_1"); assertEquals(1, gb.getCustomEnumSet().size()); @@ -97,7 +97,7 @@ public void testCustomEnumSetWithSingleValue() { @Test public void testCustomEnumSetWithMultipleValues() { - GenericBean gb = new GenericBean(); + GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("customEnumSet", new String[] {"VALUE_1", "VALUE_2"}); assertEquals(2, gb.getCustomEnumSet().size()); @@ -107,7 +107,7 @@ public void testCustomEnumSetWithMultipleValues() { @Test public void testCustomEnumSetWithMultipleValuesAsCsv() { - GenericBean gb = new GenericBean(); + GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("customEnumSet", "VALUE_1,VALUE_2"); assertEquals(2, gb.getCustomEnumSet().size()); @@ -117,7 +117,7 @@ public void testCustomEnumSetWithMultipleValuesAsCsv() { @Test public void testCustomEnumSetWithGetterSetterMismatch() { - GenericBean gb = new GenericBean(); + GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("customEnumSetMismatch", new String[] {"VALUE_1", "VALUE_2"}); assertEquals(2, gb.getCustomEnumSet().size()); @@ -127,7 +127,7 @@ public void testCustomEnumSetWithGetterSetterMismatch() { @Test public void testStandardEnumSetWithMultipleValues() { - GenericBean gb = new GenericBean(); + GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setConversionService(new DefaultConversionService()); assertNull(gb.getStandardEnumSet()); @@ -139,7 +139,7 @@ public void testStandardEnumSetWithMultipleValues() { @Test public void testStandardEnumSetWithAutoGrowing() { - GenericBean gb = new GenericBean(); + GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setAutoGrowNestedPaths(true); assertNull(gb.getStandardEnumSet()); @@ -149,11 +149,11 @@ public void testStandardEnumSetWithAutoGrowing() { @Test public void testStandardEnumMapWithMultipleValues() { - GenericBean gb = new GenericBean(); + GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setConversionService(new DefaultConversionService()); assertNull(gb.getStandardEnumMap()); - Map map = new LinkedHashMap(); + Map map = new LinkedHashMap<>(); map.put("VALUE_1", 1); map.put("VALUE_2", 2); bw.setPropertyValue("standardEnumMap", map); @@ -164,7 +164,7 @@ public void testStandardEnumMapWithMultipleValues() { @Test public void testStandardEnumMapWithAutoGrowing() { - GenericBean gb = new GenericBean(); + GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setAutoGrowNestedPaths(true); assertNull(gb.getStandardEnumMap()); @@ -173,4 +173,32 @@ public void testStandardEnumMapWithAutoGrowing() { assertEquals(new Integer(1), gb.getStandardEnumMap().get(CustomEnum.VALUE_1)); } + @Test + public void testNonPublicEnum() { + NonPublicEnumHolder holder = new NonPublicEnumHolder(); + BeanWrapper bw = new BeanWrapperImpl(holder); + bw.setPropertyValue("nonPublicEnum", "VALUE_1"); + assertEquals(NonPublicEnum.VALUE_1, holder.getNonPublicEnum()); + } + + + enum NonPublicEnum { + + VALUE_1, VALUE_2; + } + + + static class NonPublicEnumHolder { + + private NonPublicEnum nonPublicEnum; + + public NonPublicEnum getNonPublicEnum() { + return nonPublicEnum; + } + + public void setNonPublicEnum(NonPublicEnum nonPublicEnum) { + this.nonPublicEnum = nonPublicEnum; + } + } + } 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 00e41497aae4..0a61012a6018 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-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -776,6 +776,7 @@ public void testNameAlreadyBound() { private void testSingleTestBean(ListableBeanFactory lbf) { assertTrue("1 beans defined", lbf.getBeanDefinitionCount() == 1); String[] names = lbf.getBeanDefinitionNames(); + assertTrue(names != lbf.getBeanDefinitionNames()); assertTrue("Array length == 1", names.length == 1); assertTrue("0th element == test", names[0].equals("test")); TestBean tb = (TestBean) lbf.getBean("test"); @@ -1435,12 +1436,14 @@ public void testGetBeanByTypeWithAmbiguity() { public void testGetBeanByTypeWithPrimary() throws Exception { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class); + bd1.setLazyInit(true); RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class); bd2.setPrimary(true); lbf.registerBeanDefinition("bd1", bd1); lbf.registerBeanDefinition("bd2", bd2); TestBean bean = lbf.getBean(TestBean.class); assertThat(bean.getBeanName(), equalTo("bd2")); + assertFalse(lbf.containsSingleton("bd1")); } @Test @@ -1760,24 +1763,6 @@ public void testAutowireBeanByTypeWithTwoMatches() { } } - @Test - public void testAutowireBeanByTypeWithTwoMatchesAndParameterNameDiscovery() { - DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); - RootBeanDefinition bd = new RootBeanDefinition(TestBean.class); - RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class); - lbf.registerBeanDefinition("test", bd); - lbf.registerBeanDefinition("spouse", bd2); - try { - lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true); - fail("Should have thrown UnsatisfiedDependencyException"); - } - catch (UnsatisfiedDependencyException ex) { - // expected - assertTrue(ex.getMessage().contains("test")); - assertTrue(ex.getMessage().contains("spouse")); - } - } - @Test public void testAutowireBeanByTypeWithDependencyCheck() { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); @@ -2216,7 +2201,7 @@ public void testPrototypeStringCreatedRepeatedly() { @Test public void testPrototypeWithArrayConversionForConstructor() { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); - List list = new ManagedList(); + List list = new ManagedList<>(); list.add("myName"); list.add("myBeanName"); RootBeanDefinition bd = new RootBeanDefinition(DerivedTestBean.class); @@ -2235,7 +2220,7 @@ public void testPrototypeWithArrayConversionForConstructor() { @Test public void testPrototypeWithArrayConversionForFactoryMethod() { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); - List list = new ManagedList(); + List list = new ManagedList<>(); list.add("myName"); list.add("myBeanName"); RootBeanDefinition bd = new RootBeanDefinition(DerivedTestBean.class); @@ -2750,6 +2735,16 @@ public void emptyJavaUtilOptionalBean() { assertSame(Optional.empty(), bf.getBean(Optional.class)); } + @Test + public void testNonPublicEnum() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + RootBeanDefinition bd = new RootBeanDefinition(NonPublicEnumHolder.class); + bd.getConstructorArgumentValues().addGenericArgumentValue("VALUE_1"); + bf.registerBeanDefinition("holderBean", bd); + NonPublicEnumHolder holder = (NonPublicEnumHolder) bf.getBean("holderBean"); + assertEquals(NonPublicEnum.VALUE_1, holder.getNonPublicEnum()); + } + /** * Test that by-type bean lookup caching is working effectively by searching for a * bean of type B 10K times within a container having 1K additional beans of type A. @@ -3277,4 +3272,24 @@ public boolean isSingleton() { } } + + enum NonPublicEnum { + + VALUE_1, VALUE_2; + } + + + static class NonPublicEnumHolder { + + final NonPublicEnum nonPublicEnum; + + public NonPublicEnumHolder(NonPublicEnum nonPublicEnum) { + this.nonPublicEnum = nonPublicEnum; + } + + public NonPublicEnum getNonPublicEnum() { + return nonPublicEnum; + } + } + } 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 2a4afd0a9d08..9b0a22eb7103 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-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; @@ -41,6 +42,7 @@ import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; @@ -62,6 +64,7 @@ import org.springframework.tests.sample.beans.IndexedTestBean; import org.springframework.tests.sample.beans.NestedTestBean; import org.springframework.tests.sample.beans.TestBean; +import org.springframework.util.ReflectionUtils; import org.springframework.util.SerializationTestUtils; import static org.junit.Assert.*; @@ -696,8 +699,7 @@ public void testConstructorResourceInjectionWithMultipleCandidatesAsOrderedColle AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); - bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition( - ConstructorsCollectionResourceInjectionBean.class)); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ConstructorsCollectionResourceInjectionBean.class)); TestBean tb = new TestBean(); bf.registerSingleton("testBean", tb); FixedOrder2NestedTestBean ntb1 = new FixedOrder2NestedTestBean(); @@ -714,6 +716,46 @@ public void testConstructorResourceInjectionWithMultipleCandidatesAsOrderedColle bf.destroySingletons(); } + @Test + public void testSingleConstructorInjectionWithMultipleCandidatesAsOrderedCollection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SingleConstructorCollectionInjectionBean.class)); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + FixedOrder2NestedTestBean ntb1 = new FixedOrder2NestedTestBean(); + bf.registerSingleton("nestedTestBean1", ntb1); + FixedOrder1NestedTestBean ntb2 = new FixedOrder1NestedTestBean(); + bf.registerSingleton("nestedTestBean2", ntb2); + + SingleConstructorCollectionInjectionBean bean = (SingleConstructorCollectionInjectionBean) bf.getBean("annotatedBean"); + assertSame(tb, bean.getTestBean()); + assertEquals(2, bean.getNestedTestBeans().size()); + assertSame(ntb2, bean.getNestedTestBeans().get(0)); + assertSame(ntb1, bean.getNestedTestBeans().get(1)); + bf.destroySingletons(); + } + + @Test + public void testSingleConstructorInjectionWithEmptyCollection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SingleConstructorCollectionInjectionBean.class)); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + + SingleConstructorCollectionInjectionBean bean = (SingleConstructorCollectionInjectionBean) bf.getBean("annotatedBean"); + assertSame(tb, bean.getTestBean()); + assertNull(bean.getNestedTestBeans()); + bf.destroySingletons(); + } + @Test public void testConstructorResourceInjectionWithMultipleCandidatesAndFallback() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); @@ -918,7 +960,7 @@ public void testConstructorInjectionWithPlainMapAsBean() { RootBeanDefinition tbm = new RootBeanDefinition(CollectionFactoryMethods.class); tbm.setUniqueFactoryMethodName("testBeanMap"); bf.registerBeanDefinition("myTestBeanMap", tbm); - bf.registerSingleton("otherMap", new HashMap()); + bf.registerSingleton("otherMap", new HashMap<>()); MapConstructorInjectionBean bean = (MapConstructorInjectionBean) bf.getBean("annotatedBean"); assertSame(bf.getBean("myTestBeanMap"), bean.getTestBeanMap()); @@ -926,6 +968,28 @@ public void testConstructorInjectionWithPlainMapAsBean() { assertSame(bf.getBean("myTestBeanMap"), bean.getTestBeanMap()); } + @Test + public void testConstructorInjectionWithCustomMapAsBean() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(CustomMapConstructorInjectionBean.class); + bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + RootBeanDefinition tbm = new RootBeanDefinition(CustomCollectionFactoryMethods.class); + tbm.setUniqueFactoryMethodName("testBeanMap"); + bf.registerBeanDefinition("myTestBeanMap", tbm); + bf.registerSingleton("testBean1", new TestBean()); + bf.registerSingleton("testBean2", new TestBean()); + + CustomMapConstructorInjectionBean bean = (CustomMapConstructorInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("myTestBeanMap"), bean.getTestBeanMap()); + bean = (CustomMapConstructorInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("myTestBeanMap"), bean.getTestBeanMap()); + } + @Test public void testConstructorInjectionWithTypedSetAsBean() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); @@ -940,7 +1004,7 @@ public void testConstructorInjectionWithTypedSetAsBean() { tbs.add(new TestBean("tb1")); tbs.add(new TestBean("tb2")); bf.registerSingleton("testBeans", tbs); - bf.registerSingleton("otherSet", new HashSet()); + bf.registerSingleton("otherSet", new HashSet<>()); SetConstructorInjectionBean bean = (SetConstructorInjectionBean) bf.getBean("annotatedBean"); assertSame(tbs, bean.getTestBeanSet()); @@ -961,7 +1025,7 @@ public void testConstructorInjectionWithPlainSetAsBean() { RootBeanDefinition tbs = new RootBeanDefinition(CollectionFactoryMethods.class); tbs.setUniqueFactoryMethodName("testBeanSet"); bf.registerBeanDefinition("myTestBeanSet", tbs); - bf.registerSingleton("otherSet", new HashSet()); + bf.registerSingleton("otherSet", new HashSet<>()); SetConstructorInjectionBean bean = (SetConstructorInjectionBean) bf.getBean("annotatedBean"); assertSame(bf.getBean("myTestBeanSet"), bean.getTestBeanSet()); @@ -970,29 +1034,111 @@ public void testConstructorInjectionWithPlainSetAsBean() { } @Test - public void testSelfReference() { + public void testConstructorInjectionWithCustomSetAsBean() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); - RootBeanDefinition bd = new RootBeanDefinition(SelfInjectionBean.class); + RootBeanDefinition bd = new RootBeanDefinition(CustomSetConstructorInjectionBean.class); + bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); bf.registerBeanDefinition("annotatedBean", bd); + RootBeanDefinition tbs = new RootBeanDefinition(CustomCollectionFactoryMethods.class); + tbs.setUniqueFactoryMethodName("testBeanSet"); + bf.registerBeanDefinition("myTestBeanSet", tbs); + + CustomSetConstructorInjectionBean bean = (CustomSetConstructorInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("myTestBeanSet"), bean.getTestBeanSet()); + bean = (CustomSetConstructorInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("myTestBeanSet"), bean.getTestBeanSet()); + } + + @Test + public void testSelfReference() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SelfInjectionBean.class)); + + SelfInjectionBean bean = (SelfInjectionBean) bf.getBean("annotatedBean"); + assertSame(bean, bean.reference); + assertNull(bean.referenceCollection); + } + + @Test + public void testSelfReferenceWithOther() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SelfInjectionBean.class)); + bf.registerBeanDefinition("annotatedBean2", new RootBeanDefinition(SelfInjectionBean.class)); SelfInjectionBean bean = (SelfInjectionBean) bf.getBean("annotatedBean"); - assertSame(bean, bean.selfReference); + SelfInjectionBean bean2 = (SelfInjectionBean) bf.getBean("annotatedBean2"); + assertSame(bean2, bean.reference); + assertEquals(1, bean.referenceCollection.size()); + assertSame(bean2, bean.referenceCollection.get(0)); + } + + @Test + public void testSelfReferenceCollection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SelfInjectionCollectionBean.class)); + + SelfInjectionCollectionBean bean = (SelfInjectionCollectionBean) bf.getBean("annotatedBean"); + assertSame(bean, bean.reference); + assertNull(bean.referenceCollection); + } + + @Test + public void testSelfReferenceCollectionWithOther() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SelfInjectionCollectionBean.class)); + bf.registerBeanDefinition("annotatedBean2", new RootBeanDefinition(SelfInjectionCollectionBean.class)); + + SelfInjectionCollectionBean bean = (SelfInjectionCollectionBean) bf.getBean("annotatedBean"); + SelfInjectionCollectionBean bean2 = (SelfInjectionCollectionBean) bf.getBean("annotatedBean2"); + assertSame(bean2, bean.reference); + assertSame(1, bean2.referenceCollection.size()); + assertSame(bean2, bean.referenceCollection.get(0)); + } + + @Test + public void testObjectFactoryFieldInjection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryFieldInjectionBean.class)); + bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); + + ObjectFactoryFieldInjectionBean bean = (ObjectFactoryFieldInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bf.destroySingletons(); } @Test - public void testObjectFactoryInjection() { + public void testObjectFactoryConstructorInjection() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); - bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryInjectionBean.class)); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryConstructorInjectionBean.class)); bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); - ObjectFactoryInjectionBean bean = (ObjectFactoryInjectionBean) bf.getBean("annotatedBean"); + ObjectFactoryConstructorInjectionBean bean = (ObjectFactoryConstructorInjectionBean) bf.getBean("annotatedBean"); assertSame(bf.getBean("testBean"), bean.getTestBean()); bf.destroySingletons(); } @@ -1003,14 +1149,14 @@ public void testObjectFactoryInjectionIntoPrototypeBean() { AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); - RootBeanDefinition annotatedBeanDefinition = new RootBeanDefinition(ObjectFactoryInjectionBean.class); + RootBeanDefinition annotatedBeanDefinition = new RootBeanDefinition(ObjectFactoryFieldInjectionBean.class); annotatedBeanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE); bf.registerBeanDefinition("annotatedBean", annotatedBeanDefinition); bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); - ObjectFactoryInjectionBean bean = (ObjectFactoryInjectionBean) bf.getBean("annotatedBean"); + ObjectFactoryFieldInjectionBean bean = (ObjectFactoryFieldInjectionBean) bf.getBean("annotatedBean"); assertSame(bf.getBean("testBean"), bean.getTestBean()); - ObjectFactoryInjectionBean anotherBean = (ObjectFactoryInjectionBean) bf.getBean("annotatedBean"); + ObjectFactoryFieldInjectionBean anotherBean = (ObjectFactoryFieldInjectionBean) bf.getBean("annotatedBean"); assertNotSame(anotherBean, bean); assertSame(bf.getBean("testBean"), anotherBean.getTestBean()); } @@ -1025,27 +1171,48 @@ public void testObjectFactoryQualifierInjection() { bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryQualifierInjectionBean.class)); RootBeanDefinition bd = new RootBeanDefinition(TestBean.class); bd.addQualifier(new AutowireCandidateQualifier(Qualifier.class, "testBean")); - bf.registerBeanDefinition("testBean", bd); - bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class)); + bf.registerBeanDefinition("dependencyBean", bd); + bf.registerBeanDefinition("dependencyBean2", new RootBeanDefinition(TestBean.class)); ObjectFactoryQualifierInjectionBean bean = (ObjectFactoryQualifierInjectionBean) bf.getBean("annotatedBean"); - assertSame(bf.getBean("testBean"), bean.getTestBean()); + assertSame(bf.getBean("dependencyBean"), bean.getTestBean()); + bf.destroySingletons(); + } + + @Test + public void testObjectFactoryQualifierProviderInjection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryQualifierInjectionBean.class)); + RootBeanDefinition bd = new RootBeanDefinition(TestBean.class); + bd.setQualifiedElement(ReflectionUtils.findMethod(getClass(), "testBeanQualifierProvider")); + bf.registerBeanDefinition("dependencyBean", bd); + bf.registerBeanDefinition("dependencyBean2", new RootBeanDefinition(TestBean.class)); + + ObjectFactoryQualifierInjectionBean bean = (ObjectFactoryQualifierInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("dependencyBean"), bean.getTestBean()); bf.destroySingletons(); } + @Qualifier("testBean") + private void testBeanQualifierProvider() {} + @Test public void testObjectFactorySerialization() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); - bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryInjectionBean.class)); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryFieldInjectionBean.class)); bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); bf.setSerializationId("test"); - ObjectFactoryInjectionBean bean = (ObjectFactoryInjectionBean) bf.getBean("annotatedBean"); + ObjectFactoryFieldInjectionBean bean = (ObjectFactoryFieldInjectionBean) bf.getBean("annotatedBean"); assertSame(bf.getBean("testBean"), bean.getTestBean()); - bean = (ObjectFactoryInjectionBean) SerializationTestUtils.serializeAndDeserialize(bean); + bean = (ObjectFactoryFieldInjectionBean) SerializationTestUtils.serializeAndDeserialize(bean); assertSame(bf.getBean("testBean"), bean.getTestBean()); bf.destroySingletons(); } @@ -1145,12 +1312,15 @@ public void testSmartObjectFactoryInjectionWithTargetPrimary() { RootBeanDefinition tb1 = new RootBeanDefinition(TestBean.class); tb1.setPrimary(true); bf.registerBeanDefinition("testBean1", tb1); - bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class)); + RootBeanDefinition tb2 = new RootBeanDefinition(TestBean.class); + tb2.setLazyInit(true); + bf.registerBeanDefinition("testBean2", tb2); SmartObjectFactoryInjectionBean bean = (SmartObjectFactoryInjectionBean) bf.getBean("annotatedBean"); assertSame(bf.getBean("testBean1"), bean.getTestBean()); assertSame(bf.getBean("testBean1"), bean.getOptionalTestBean()); assertSame(bf.getBean("testBean1"), bean.getUniqueTestBean()); + assertFalse(bf.containsSingleton("testBean2")); bf.destroySingletons(); } @@ -1220,7 +1390,6 @@ public void testCustomAnnotationRequiredFieldResourceInjectionFailsWhenMultipleD } catch (UnsatisfiedDependencyException ex) { // expected - ex.printStackTrace(); assertSame(CustomAnnotationRequiredFieldResourceInjectionBean.class, ex.getInjectionPoint().getField().getDeclaringClass()); } @@ -1476,12 +1645,30 @@ public void testGenericsBasedFieldInjection() { RootBeanDefinition bd = new RootBeanDefinition(RepositoryFieldInjectionBean.class); bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); bf.registerBeanDefinition("annotatedBean", bd); + String sv = "X"; + bf.registerSingleton("stringValue", sv); + Integer iv = 1; + bf.registerSingleton("integerValue", iv); StringRepository sr = new StringRepository(); bf.registerSingleton("stringRepo", sr); IntegerRepository ir = new IntegerRepository(); bf.registerSingleton("integerRepo", ir); RepositoryFieldInjectionBean bean = (RepositoryFieldInjectionBean) bf.getBean("annotatedBean"); + assertSame(sv, bean.string); + assertSame(iv, bean.integer); + assertSame(1, bean.stringArray.length); + assertSame(1, bean.integerArray.length); + assertSame(sv, bean.stringArray[0]); + assertSame(iv, bean.integerArray[0]); + assertSame(1, bean.stringList.size()); + assertSame(1, bean.integerList.size()); + assertSame(sv, bean.stringList.get(0)); + assertSame(iv, bean.integerList.get(0)); + assertSame(1, bean.stringMap.size()); + assertSame(1, bean.integerMap.size()); + assertSame(sv, bean.stringMap.get("stringValue")); + assertSame(iv, bean.integerMap.get("integerValue")); assertSame(sr, bean.stringRepository); assertSame(ir, bean.integerRepository); assertSame(1, bean.stringRepositoryArray.length); @@ -1508,12 +1695,30 @@ public void testGenericsBasedFieldInjectionWithSubstitutedVariables() { RootBeanDefinition bd = new RootBeanDefinition(RepositoryFieldInjectionBeanWithSubstitutedVariables.class); bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); bf.registerBeanDefinition("annotatedBean", bd); + String sv = "X"; + bf.registerSingleton("stringValue", sv); + Integer iv = 1; + bf.registerSingleton("integerValue", iv); StringRepository sr = new StringRepository(); bf.registerSingleton("stringRepo", sr); IntegerRepository ir = new IntegerRepository(); bf.registerSingleton("integerRepo", ir); RepositoryFieldInjectionBeanWithSubstitutedVariables bean = (RepositoryFieldInjectionBeanWithSubstitutedVariables) bf.getBean("annotatedBean"); + assertSame(sv, bean.string); + assertSame(iv, bean.integer); + assertSame(1, bean.stringArray.length); + assertSame(1, bean.integerArray.length); + assertSame(sv, bean.stringArray[0]); + assertSame(iv, bean.integerArray[0]); + assertSame(1, bean.stringList.size()); + assertSame(1, bean.integerList.size()); + assertSame(sv, bean.stringList.get(0)); + assertSame(iv, bean.integerList.get(0)); + assertSame(1, bean.stringMap.size()); + assertSame(1, bean.integerMap.size()); + assertSame(sv, bean.stringMap.get("stringValue")); + assertSame(iv, bean.integerMap.get("integerValue")); assertSame(sr, bean.stringRepository); assertSame(ir, bean.integerRepository); assertSame(1, bean.stringRepositoryArray.length); @@ -1584,11 +1789,12 @@ public void testGenericsBasedFieldInjectionWithMocks() { rbd.setFactoryBeanName("mocksControl"); rbd.setFactoryMethodName("createMock"); rbd.getConstructorArgumentValues().addGenericArgumentValue(Repository.class); - bf.registerBeanDefinition("integerRepo", rbd); + rbd.setQualifiedElement(ReflectionUtils.findField(getClass(), "integerRepositoryQualifierProvider")); + bf.registerBeanDefinition("integerRepository", rbd); // Bean name not matching qualifier RepositoryFieldInjectionBeanWithQualifiers bean = (RepositoryFieldInjectionBeanWithQualifiers) bf.getBean("annotatedBean"); Repository sr = bf.getBean("stringRepo", Repository.class); - Repository ir = bf.getBean("integerRepo", Repository.class); + Repository ir = bf.getBean("integerRepository", Repository.class); assertSame(sr, bean.stringRepository); assertSame(ir, bean.integerRepository); assertSame(1, bean.stringRepositoryArray.length); @@ -1602,7 +1808,7 @@ public void testGenericsBasedFieldInjectionWithMocks() { assertSame(1, bean.stringRepositoryMap.size()); assertSame(1, bean.integerRepositoryMap.size()); assertSame(sr, bean.stringRepositoryMap.get("stringRepo")); - assertSame(ir, bean.integerRepositoryMap.get("integerRepo")); + assertSame(ir, bean.integerRepositoryMap.get("integerRepository")); } @Test @@ -1755,12 +1961,30 @@ public void testGenericsBasedMethodInjection() { RootBeanDefinition bd = new RootBeanDefinition(RepositoryMethodInjectionBean.class); bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); bf.registerBeanDefinition("annotatedBean", bd); + String sv = "X"; + bf.registerSingleton("stringValue", sv); + Integer iv = 1; + bf.registerSingleton("integerValue", iv); StringRepository sr = new StringRepository(); bf.registerSingleton("stringRepo", sr); IntegerRepository ir = new IntegerRepository(); bf.registerSingleton("integerRepo", ir); RepositoryMethodInjectionBean bean = (RepositoryMethodInjectionBean) bf.getBean("annotatedBean"); + assertSame(sv, bean.string); + assertSame(iv, bean.integer); + assertSame(1, bean.stringArray.length); + assertSame(1, bean.integerArray.length); + assertSame(sv, bean.stringArray[0]); + assertSame(iv, bean.integerArray[0]); + assertSame(1, bean.stringList.size()); + assertSame(1, bean.integerList.size()); + assertSame(sv, bean.stringList.get(0)); + assertSame(iv, bean.integerList.get(0)); + assertSame(1, bean.stringMap.size()); + assertSame(1, bean.integerMap.size()); + assertSame(sv, bean.stringMap.get("stringValue")); + assertSame(iv, bean.integerMap.get("integerValue")); assertSame(sr, bean.stringRepository); assertSame(ir, bean.integerRepository); assertSame(1, bean.stringRepositoryArray.length); @@ -1787,12 +2011,30 @@ public void testGenericsBasedMethodInjectionWithSubstitutedVariables() { RootBeanDefinition bd = new RootBeanDefinition(RepositoryMethodInjectionBeanWithSubstitutedVariables.class); bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); bf.registerBeanDefinition("annotatedBean", bd); + String sv = "X"; + bf.registerSingleton("stringValue", sv); + Integer iv = 1; + bf.registerSingleton("integerValue", iv); StringRepository sr = new StringRepository(); bf.registerSingleton("stringRepo", sr); IntegerRepository ir = new IntegerRepository(); bf.registerSingleton("integerRepo", ir); RepositoryMethodInjectionBeanWithSubstitutedVariables bean = (RepositoryMethodInjectionBeanWithSubstitutedVariables) bf.getBean("annotatedBean"); + assertSame(sv, bean.string); + assertSame(iv, bean.integer); + assertSame(1, bean.stringArray.length); + assertSame(1, bean.integerArray.length); + assertSame(sv, bean.stringArray[0]); + assertSame(iv, bean.integerArray[0]); + assertSame(1, bean.stringList.size()); + assertSame(1, bean.integerList.size()); + assertSame(sv, bean.stringList.get(0)); + assertSame(iv, bean.integerList.get(0)); + assertSame(1, bean.stringMap.size()); + assertSame(1, bean.integerMap.size()); + assertSame(sv, bean.stringMap.get("stringValue")); + assertSame(iv, bean.integerMap.get("integerValue")); assertSame(sr, bean.stringRepository); assertSame(ir, bean.integerRepository); assertSame(1, bean.stringRepositoryArray.length); @@ -2047,6 +2289,24 @@ public void testGenericsBasedInjectionIntoTypeVariableSelectingBestMatchAgainstF assertSame(bean2, bean1.gi2); } + @Test + public void testGenericsBasedInjectionWithBeanDefinitionTargetResolvableType() throws Exception { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd1 = new RootBeanDefinition(GenericInterface2Bean.class); + bd1.setTargetType(ResolvableType.forClassWithGenerics(GenericInterface2Bean.class, String.class)); + bf.registerBeanDefinition("bean1", bd1); + RootBeanDefinition bd2 = new RootBeanDefinition(GenericInterface2Bean.class); + bd2.setTargetType(ResolvableType.forClassWithGenerics(GenericInterface2Bean.class, Integer.class)); + bf.registerBeanDefinition("bean2", bd2); + bf.registerBeanDefinition("bean3", new RootBeanDefinition(MultiGenericFieldInjection.class)); + + assertEquals("bean1 a bean2 123", bf.getBean("bean3").toString()); + } + @Test public void testCircularTypeReference() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); @@ -2087,6 +2347,45 @@ public void testSingleConstructorWithProvidedArgument() { assertNotNull(bf.getBean(ProvidedArgumentBean.class)); } + @Test + public void testAnnotatedDefaultConstructor() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor()); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(AnnotatedDefaultConstructorBean.class)); + + assertNotNull(bf.getBean("annotatedBean")); + } + + @Test // SPR-15125 + public void testFactoryBeanSelfInjection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SelfInjectingFactoryBean.class)); + + SelfInjectingFactoryBean bean = bf.getBean(SelfInjectingFactoryBean.class); + assertSame(bf.getBean("annotatedBean"), bean.testBean); + } + + @Test // SPR-15125 + public void testFactoryBeanSelfInjectionViaFactoryMethod() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(SelfInjectingFactoryBean.class); + bd.setFactoryMethodName("create"); + bf.registerBeanDefinition("annotatedBean", bd); + + SelfInjectingFactoryBean bean = bf.getBean(SelfInjectingFactoryBean.class); + assertSame(bf.getBean("annotatedBean"), bean.testBean); + } + + + @Qualifier("integerRepo") + private Repository integerRepositoryQualifierProvider; + public static class ResourceInjectionBean { @@ -2491,6 +2790,28 @@ public List getNestedTestBeans() { } + public static class SingleConstructorCollectionInjectionBean { + + private ITestBean testBean; + + private List nestedTestBeans; + + public SingleConstructorCollectionInjectionBean(ITestBean testBean, + @Autowired(required = false) List nestedTestBeans) { + this.testBean = testBean; + this.nestedTestBeans = nestedTestBeans; + } + + public ITestBean getTestBean() { + return this.testBean; + } + + public List getNestedTestBeans() { + return this.nestedTestBeans; + } + } + + @SuppressWarnings("serial") public static class MyTestBeanMap extends LinkedHashMap { } @@ -2534,7 +2855,21 @@ public Set getTestBeanSet() { public static class SelfInjectionBean { @Autowired - public SelfInjectionBean selfReference; + public SelfInjectionBean reference; + + @Autowired(required = false) + public List referenceCollection; + } + + + @SuppressWarnings("serial") + public static class SelfInjectionCollectionBean extends LinkedList { + + @Autowired + public SelfInjectionCollectionBean reference; + + @Autowired(required = false) + public List referenceCollection; } @@ -2572,7 +2907,7 @@ public Map getTestBeanMap() { @SuppressWarnings("serial") - public static class ObjectFactoryInjectionBean implements Serializable { + public static class ObjectFactoryFieldInjectionBean implements Serializable { @Autowired private ObjectFactory testBeanFactory; @@ -2583,6 +2918,21 @@ public TestBean getTestBean() { } + @SuppressWarnings("serial") + public static class ObjectFactoryConstructorInjectionBean implements Serializable { + + private final ObjectFactory testBeanFactory; + + public ObjectFactoryConstructorInjectionBean(ObjectFactory testBeanFactory) { + this.testBeanFactory = testBeanFactory; + } + + public TestBean getTestBean() { + return this.testBeanFactory.getObject(); + } + } + + public static class ObjectFactoryQualifierInjectionBean { @Autowired @@ -2780,6 +3130,30 @@ public boolean isSingleton() { public static class RepositoryFieldInjectionBean { + @Autowired + public String string; + + @Autowired + public Integer integer; + + @Autowired + public String[] stringArray; + + @Autowired + public Integer[] integerArray; + + @Autowired + public List stringList; + + @Autowired + public List integerList; + + @Autowired + public Map stringMap; + + @Autowired + public Map integerMap; + @Autowired public Repository stringRepository; @@ -2808,6 +3182,30 @@ public static class RepositoryFieldInjectionBean { public static class RepositoryFieldInjectionBeanWithVariables { + @Autowired + public S string; + + @Autowired + public I integer; + + @Autowired + public S[] stringArray; + + @Autowired + public I[] integerArray; + + @Autowired + public List stringList; + + @Autowired + public List integerList; + + @Autowired + public Map stringMap; + + @Autowired + public Map integerMap; + @Autowired public Repository stringRepository; @@ -2904,6 +3302,22 @@ public static class RepositoryFactoryBeanInjectionBean { public static class RepositoryMethodInjectionBean { + public String string; + + public Integer integer; + + public String[] stringArray; + + public Integer[] integerArray; + + public List stringList; + + public List integerList; + + public Map stringMap; + + public Map integerMap; + public Repository stringRepository; public Repository integerRepository; @@ -2920,6 +3334,46 @@ public static class RepositoryMethodInjectionBean { public Map> integerRepositoryMap; + @Autowired + public void setString(String string) { + this.string = string; + } + + @Autowired + public void setInteger(Integer integer) { + this.integer = integer; + } + + @Autowired + public void setStringArray(String[] stringArray) { + this.stringArray = stringArray; + } + + @Autowired + public void setIntegerArray(Integer[] integerArray) { + this.integerArray = integerArray; + } + + @Autowired + public void setStringList(List stringList) { + this.stringList = stringList; + } + + @Autowired + public void setIntegerList(List integerList) { + this.integerList = integerList; + } + + @Autowired + public void setStringMap(Map stringMap) { + this.stringMap = stringMap; + } + + @Autowired + public void setIntegerMap(Map integerMap) { + this.integerMap = integerMap; + } + @Autowired public void setStringRepository(Repository stringRepository) { this.stringRepository = stringRepository; @@ -2964,6 +3418,22 @@ public void setIntegerRepositoryMap(Map> integerRepo public static class RepositoryMethodInjectionBeanWithVariables { + public S string; + + public I integer; + + public S[] stringArray; + + public I[] integerArray; + + public List stringList; + + public List integerList; + + public Map stringMap; + + public Map integerMap; + public Repository stringRepository; public Repository integerRepository; @@ -2980,6 +3450,46 @@ public static class RepositoryMethodInjectionBeanWithVariables { public Map> integerRepositoryMap; + @Autowired + public void setString(S string) { + this.string = string; + } + + @Autowired + public void setInteger(I integer) { + this.integer = integer; + } + + @Autowired + public void setStringArray(S[] stringArray) { + this.stringArray = stringArray; + } + + @Autowired + public void setIntegerArray(I[] integerArray) { + this.integerArray = integerArray; + } + + @Autowired + public void setStringList(List stringList) { + this.stringList = stringList; + } + + @Autowired + public void setIntegerList(List integerList) { + this.integerList = integerList; + } + + @Autowired + public void setStringMap(Map stringMap) { + this.stringMap = stringMap; + } + + @Autowired + public void setIntegerMap(Map integerMap) { + this.integerMap = integerMap; + } + @Autowired public void setStringRepository(Repository stringRepository) { this.stringRepository = stringRepository; @@ -3101,7 +3611,7 @@ public static GenericInterface1 create() { } public static GenericInterface1 createErased() { - return new GenericInterface1Impl(); + return new GenericInterface1Impl<>(); } @SuppressWarnings("rawtypes") @@ -3117,7 +3627,7 @@ public static class StringGenericInterface1Impl extends GenericInterface1Impl { - public String doSomethingMoreGeneric(K o); + String doSomethingMoreGeneric(K o); } @@ -3139,6 +3649,37 @@ public String doSomethingMoreGeneric(Object o) { } + public static class GenericInterface2Bean implements GenericInterface2, BeanNameAware { + + private String name; + + @Override + public void setBeanName(String name) { + this.name = name; + } + + @Override + public String doSomethingMoreGeneric(K o) { + return this.name + " " + o; + } + } + + + public static class MultiGenericFieldInjection { + + @Autowired + private GenericInterface2 stringBean; + + @Autowired + private GenericInterface2 integerBean; + + @Override + public String toString() { + return this.stringBean.doSomethingMoreGeneric("a") + " " + this.integerBean.doSomethingMoreGeneric(123); + } + } + + @SuppressWarnings("rawtypes") public static class PlainGenericInterface2Impl implements GenericInterface2 { @@ -3257,19 +3798,120 @@ public ProvidedArgumentBean(String[] args) { public static class CollectionFactoryMethods { public static Map testBeanMap() { - Map tbm = new LinkedHashMap(); + Map tbm = new LinkedHashMap<>(); tbm.put("testBean1", new TestBean("tb1")); tbm.put("testBean2", new TestBean("tb2")); return tbm; } public static Set testBeanSet() { - Set tbs = new LinkedHashSet(); + Set tbs = new LinkedHashSet<>(); + tbs.add(new TestBean("tb1")); + tbs.add(new TestBean("tb2")); + return tbs; + } + } + + + public static class CustomCollectionFactoryMethods { + + public static CustomMap testBeanMap() { + CustomMap tbm = new CustomHashMap<>(); + tbm.put("testBean1", new TestBean("tb1")); + tbm.put("testBean2", new TestBean("tb2")); + return tbm; + } + + public static CustomSet testBeanSet() { + CustomSet tbs = new CustomHashSet<>(); tbs.add(new TestBean("tb1")); tbs.add(new TestBean("tb2")); return tbs; } + } + + + public static class CustomMapConstructorInjectionBean { + + private CustomMap testBeanMap; + + @Autowired + public CustomMapConstructorInjectionBean(CustomMap testBeanMap) { + this.testBeanMap = testBeanMap; + } + + public CustomMap getTestBeanMap() { + return this.testBeanMap; + } + } + + + public static class CustomSetConstructorInjectionBean { + + private CustomSet testBeanSet; + + @Autowired + public CustomSetConstructorInjectionBean(CustomSet testBeanSet) { + this.testBeanSet = testBeanSet; + } + + public CustomSet getTestBeanSet() { + return this.testBeanSet; + } + } + + + public interface CustomMap extends Map { + } + + + @SuppressWarnings("serial") + public static class CustomHashMap extends LinkedHashMap implements CustomMap { + } + + + public interface CustomSet extends Set { + } + + + @SuppressWarnings("serial") + public static class CustomHashSet extends LinkedHashSet implements CustomSet { + } + + + public static class AnnotatedDefaultConstructorBean { + + @Autowired + public AnnotatedDefaultConstructorBean() { + } + } + + + public static class SelfInjectingFactoryBean implements FactoryBean { + + private final TestBean exposedTestBean = new TestBean(); + + @Autowired + TestBean testBean; + + @Override + public TestBean getObject() { + return exposedTestBean; + } + + @Override + public Class getObjectType() { + return TestBean.class; + } + @Override + public boolean isSingleton() { + return true; + } + + public static SelfInjectingFactoryBean create() { + return new SelfInjectingFactoryBean(); + } } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java index f1bea66f2bb0..347a9b57c5f1 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,7 +45,7 @@ /** * Unit tests for {@link org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor} - * processing the JSR-303 {@link javax.inject.Inject} annotation. + * processing the JSR-330 {@link javax.inject.Inject} annotation. * * @author Juergen Hoeller * @since 3.0 @@ -548,11 +548,9 @@ public void testObjectFactoryWithTypedMapMethod() throws Exception { } /** - * Verifies that a dependency on a {@link org.springframework.beans.factory.FactoryBean} can be autowired via - * {@link org.springframework.beans.factory.annotation.Autowired @Inject}, specifically addressing the JIRA issue - * raised in SPR-4040. + * Verifies that a dependency on a {@link org.springframework.beans.factory.FactoryBean} + * can be autowired via {@link org.springframework.beans.factory.annotation.Autowired @Inject}, + * specifically addressing SPR-4040. */ @Test public void testBeanAutowiredWithFactoryBean() { @@ -742,6 +740,15 @@ public void testProviderOfOptionalMethodInjectionWithBeanNotAvailable() { bf.destroySingletons(); } + @Test + public void testAnnotatedDefaultConstructor() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor()); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(AnnotatedDefaultConstructorBean.class)); + + assertNotNull(bf.getBean("annotatedBean")); + } + public static class ResourceInjectionBean { @@ -750,7 +757,6 @@ public static class ResourceInjectionBean { private TestBean testBean2; - @Inject public void setTestBean2(TestBean testBean2) { if (this.testBean2 != null) { @@ -819,7 +825,6 @@ public BeanFactory getBeanFactory() { public static class TypedExtendedResourceInjectionBean extends ExtendedResourceInjectionBean { - } @@ -1087,7 +1092,6 @@ public static class MapFieldInjectionBean { @Inject private Map testBeanMap; - public Map getTestBeanMap() { return this.testBeanMap; } @@ -1253,7 +1257,7 @@ public final FactoryBean getFactoryBean() { public static class StringFactoryBean implements FactoryBean { @Override - public String getObject() throws Exception { + public String getObject() { return ""; } @@ -1285,8 +1289,8 @@ public static class OptionalMethodInjectionBean { private Optional testBean; @Inject - public void setTestBean(Optional testBeanFactory) { - this.testBean = testBeanFactory; + public void setTestBean(Optional testBean) { + this.testBean = testBean; } public Optional getTestBean() { @@ -1311,8 +1315,8 @@ public static class OptionalListMethodInjectionBean { private Optional> testBean; @Inject - public void setTestBean(Optional> testBeanFactory) { - this.testBean = testBeanFactory; + public void setTestBean(Optional> testBean) { + this.testBean = testBean; } public Optional> getTestBean() { @@ -1337,8 +1341,8 @@ public static class ProviderOfOptionalMethodInjectionBean { private Provider> testBean; @Inject - public void setTestBean(Provider> testBeanFactory) { - this.testBean = testBeanFactory; + public void setTestBean(Provider> testBean) { + this.testBean = testBean; } public Optional getTestBean() { @@ -1346,4 +1350,12 @@ public Optional getTestBean() { } } + + public static class AnnotatedDefaultConstructorBean { + + @Inject + public AnnotatedDefaultConstructorBean() { + } + } + } 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 e3355453ccd6..0f5b5ef2e412 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-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ public void setUp() { aabpp.setBeanFactory(beanFactory); beanFactory.addBeanPostProcessor(aabpp); beanFactory.registerBeanDefinition("abstractBean", new RootBeanDefinition(AbstractBean.class)); + beanFactory.registerBeanDefinition("beanConsumer", new RootBeanDefinition(BeanConsumer.class)); RootBeanDefinition tbd = new RootBeanDefinition(TestBean.class); tbd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); beanFactory.registerBeanDefinition("testBean", tbd); @@ -53,6 +54,7 @@ public void testWithoutConstructorArg() { assertNotNull(bean); Object expected = bean.get(); assertEquals(TestBean.class, expected.getClass()); + assertSame(bean, beanFactory.getBean(BeanConsumer.class).abstractBean); } @Test @@ -62,6 +64,7 @@ public void testWithOverloadedArg() { TestBean expected = bean.get("haha"); assertEquals(TestBean.class, expected.getClass()); assertEquals("haha", expected.getName()); + assertSame(bean, beanFactory.getBean(BeanConsumer.class).abstractBean); } @Test @@ -71,6 +74,7 @@ public void testWithOneConstructorArg() { TestBean expected = bean.getOneArgument("haha"); assertEquals(TestBean.class, expected.getClass()); assertEquals("haha", expected.getName()); + assertSame(bean, beanFactory.getBean(BeanConsumer.class).abstractBean); } @Test @@ -81,6 +85,7 @@ public void testWithTwoConstructorArg() { assertEquals(TestBean.class, expected.getClass()); assertEquals("haha", expected.getName()); assertEquals(72, expected.getAge()); + assertSame(bean, beanFactory.getBean(BeanConsumer.class).abstractBean); } @Test @@ -93,6 +98,16 @@ public void testWithThreeArgsShouldFail() { } catch (AbstractMethodError ex) { } + assertSame(bean, beanFactory.getBean(BeanConsumer.class).abstractBean); + } + + @Test + public void testWithEarlyInjection() { + AbstractBean bean = beanFactory.getBean("beanConsumer", BeanConsumer.class).abstractBean; + assertNotNull(bean); + Object expected = bean.get(); + assertEquals(TestBean.class, expected.getClass()); + assertSame(bean, beanFactory.getBean(BeanConsumer.class).abstractBean); } @@ -113,4 +128,11 @@ public static abstract class AbstractBean { public abstract TestBean getThreeArguments(String name, int age, int anotherArg); } + + public static class BeanConsumer { + + @Autowired + AbstractBean abstractBean; + } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/CustomScopeConfigurerTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/CustomScopeConfigurerTests.java index 25a9a14a3811..4c45809067b5 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/CustomScopeConfigurerTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/CustomScopeConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import java.util.HashMap; import java.util.Map; -import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.support.DefaultListableBeanFactory; @@ -34,27 +33,24 @@ * @author Juergen Hoeller * @author Chris Beams */ -public final class CustomScopeConfigurerTests { +public class CustomScopeConfigurerTests { private static final String FOO_SCOPE = "fooScope"; - private ConfigurableListableBeanFactory factory; - @Before - public void setUp() { - factory = new DefaultListableBeanFactory(); - } + private final ConfigurableListableBeanFactory factory = new DefaultListableBeanFactory(); + @Test - public void testWithNoScopes() throws Exception { + public void testWithNoScopes() { CustomScopeConfigurer figurer = new CustomScopeConfigurer(); figurer.postProcessBeanFactory(factory); } @Test - public void testSunnyDayWithBonaFideScopeInstance() throws Exception { + public void testSunnyDayWithBonaFideScopeInstance() { Scope scope = mock(Scope.class); factory.registerScope(FOO_SCOPE, scope); - Map scopes = new HashMap(); + Map scopes = new HashMap<>(); scopes.put(FOO_SCOPE, scope); CustomScopeConfigurer figurer = new CustomScopeConfigurer(); figurer.setScopes(scopes); @@ -62,8 +58,8 @@ public void testSunnyDayWithBonaFideScopeInstance() throws Exception { } @Test - public void testSunnyDayWithBonaFideScopeClass() throws Exception { - Map scopes = new HashMap(); + public void testSunnyDayWithBonaFideScopeClass() { + Map scopes = new HashMap<>(); scopes.put(FOO_SCOPE, NoOpScope.class); CustomScopeConfigurer figurer = new CustomScopeConfigurer(); figurer.setScopes(scopes); @@ -72,8 +68,8 @@ public void testSunnyDayWithBonaFideScopeClass() throws Exception { } @Test - public void testSunnyDayWithBonaFideScopeClassname() throws Exception { - Map scopes = new HashMap(); + public void testSunnyDayWithBonaFideScopeClassName() { + Map scopes = new HashMap<>(); scopes.put(FOO_SCOPE, NoOpScope.class.getName()); CustomScopeConfigurer figurer = new CustomScopeConfigurer(); figurer.setScopes(scopes); @@ -81,29 +77,29 @@ public void testSunnyDayWithBonaFideScopeClassname() throws Exception { assertTrue(factory.getRegisteredScope(FOO_SCOPE) instanceof NoOpScope); } - @Test(expected=IllegalArgumentException.class) - public void testWhereScopeMapHasNullScopeValueInEntrySet() throws Exception { - Map scopes = new HashMap(); + @Test(expected = IllegalArgumentException.class) + public void testWhereScopeMapHasNullScopeValueInEntrySet() { + Map scopes = new HashMap<>(); scopes.put(FOO_SCOPE, null); CustomScopeConfigurer figurer = new CustomScopeConfigurer(); figurer.setScopes(scopes); figurer.postProcessBeanFactory(factory); } - @Test(expected=IllegalArgumentException.class) - public void testWhereScopeMapHasNonScopeInstanceInEntrySet() throws Exception { - Map scopes = new HashMap(); - scopes.put(FOO_SCOPE, this); // <-- not a valid value... + @Test(expected = IllegalArgumentException.class) + public void testWhereScopeMapHasNonScopeInstanceInEntrySet() { + Map scopes = new HashMap<>(); + scopes.put(FOO_SCOPE, this); // <-- not a valid value... CustomScopeConfigurer figurer = new CustomScopeConfigurer(); figurer.setScopes(scopes); figurer.postProcessBeanFactory(factory); } @SuppressWarnings("unchecked") - @Test(expected=ClassCastException.class) - public void testWhereScopeMapHasNonStringTypedScopeNameInKeySet() throws Exception { + @Test(expected = ClassCastException.class) + public void testWhereScopeMapHasNonStringTypedScopeNameInKeySet() { Map scopes = new HashMap(); - scopes.put(this, new NoOpScope()); // <-- not a valid value (the key)... + scopes.put(this, new NoOpScope()); // <-- not a valid value (the key)... CustomScopeConfigurer figurer = new CustomScopeConfigurer(); figurer.setScopes(scopes); figurer.postProcessBeanFactory(factory); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/DeprecatedBeanWarnerTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/DeprecatedBeanWarnerTests.java index 4ed025b09b65..07752bf048ea 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/DeprecatedBeanWarnerTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/DeprecatedBeanWarnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,28 +28,23 @@ */ public class DeprecatedBeanWarnerTests { - private DefaultListableBeanFactory beanFactory; - private String beanName; private BeanDefinition beanDefinition; - private DeprecatedBeanWarner warner; - @Test @SuppressWarnings("deprecation") public void postProcess() { - beanFactory = new DefaultListableBeanFactory(); + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); BeanDefinition def = new RootBeanDefinition(MyDeprecatedBean.class); String beanName = "deprecated"; beanFactory.registerBeanDefinition(beanName, def); - warner = new MyDeprecatedBeanWarner(); + DeprecatedBeanWarner warner = new MyDeprecatedBeanWarner(); warner.postProcessBeanFactory(beanFactory); assertEquals(beanName, this.beanName); assertEquals(def, this.beanDefinition); - } 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 4d5b39e569ea..8d0786bada52 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -140,7 +140,7 @@ public void testGetObjectType() throws Exception { mcfb.setTargetMethod("voidRetvalMethod"); mcfb.afterPropertiesSet(); Class objType = mcfb.getObjectType(); - assertTrue(objType.equals(void.class)); + assertSame(objType, void.class); // verify that we can call a method with args that are subtypes of the // target method arg types @@ -148,7 +148,7 @@ public void testGetObjectType() throws Exception { mcfb = new MethodInvokingFactoryBean(); mcfb.setTargetClass(TestClass1.class); mcfb.setTargetMethod("supertypes"); - mcfb.setArguments(new Object[] {new ArrayList(), new ArrayList(), "hello"}); + mcfb.setArguments(new ArrayList<>(), new ArrayList(), "hello"); mcfb.afterPropertiesSet(); mcfb.getObjectType(); @@ -157,7 +157,7 @@ public void testGetObjectType() throws Exception { mcfb.registerCustomEditor(String.class, new StringTrimmerEditor(false)); mcfb.setTargetClass(TestClass1.class); mcfb.setTargetMethod("supertypes"); - mcfb.setArguments(new Object[] {"1", new Object()}); + mcfb.setArguments("1", new Object()); try { mcfb.afterPropertiesSet(); fail("Should have thrown NoSuchMethodException"); @@ -225,7 +225,7 @@ public void testGetObject() throws Exception { mcfb = new MethodInvokingFactoryBean(); mcfb.setTargetClass(TestClass1.class); mcfb.setTargetMethod("supertypes"); - mcfb.setArguments(new Object[] {new ArrayList(), new ArrayList(), "hello"}); + mcfb.setArguments(new ArrayList<>(), new ArrayList(), "hello"); // should pass mcfb.afterPropertiesSet(); } @@ -235,7 +235,7 @@ public void testArgumentConversion() throws Exception { MethodInvokingFactoryBean mcfb = new MethodInvokingFactoryBean(); mcfb.setTargetClass(TestClass1.class); mcfb.setTargetMethod("supertypes"); - mcfb.setArguments(new Object[] {new ArrayList(), new ArrayList(), "hello", "bogus"}); + mcfb.setArguments(new ArrayList<>(), new ArrayList(), "hello", "bogus"); try { mcfb.afterPropertiesSet(); fail("Matched method with wrong number of args"); @@ -247,7 +247,7 @@ public void testArgumentConversion() throws Exception { mcfb = new MethodInvokingFactoryBean(); mcfb.setTargetClass(TestClass1.class); mcfb.setTargetMethod("supertypes"); - mcfb.setArguments(new Object[] {1, new Object()}); + mcfb.setArguments(1, new Object()); try { mcfb.afterPropertiesSet(); mcfb.getObject(); @@ -260,14 +260,14 @@ public void testArgumentConversion() throws Exception { mcfb = new MethodInvokingFactoryBean(); mcfb.setTargetClass(TestClass1.class); mcfb.setTargetMethod("supertypes2"); - mcfb.setArguments(new Object[] {new ArrayList(), new ArrayList(), "hello", "bogus"}); + mcfb.setArguments(new ArrayList<>(), new ArrayList(), "hello", "bogus"); mcfb.afterPropertiesSet(); assertEquals("hello", mcfb.getObject()); mcfb = new MethodInvokingFactoryBean(); mcfb.setTargetClass(TestClass1.class); mcfb.setTargetMethod("supertypes2"); - mcfb.setArguments(new Object[] {new ArrayList(), new ArrayList(), new Object()}); + mcfb.setArguments(new ArrayList<>(), new ArrayList(), new Object()); try { mcfb.afterPropertiesSet(); fail("Matched method when shouldn't have matched"); @@ -292,14 +292,14 @@ public void testInvokeWithIntArgument() throws Exception { ArgumentConvertingMethodInvoker methodInvoker = new ArgumentConvertingMethodInvoker(); methodInvoker.setTargetClass(TestClass1.class); methodInvoker.setTargetMethod("intArgument"); - methodInvoker.setArguments(new Object[] {5}); + methodInvoker.setArguments(5); methodInvoker.prepare(); methodInvoker.invoke(); methodInvoker = new ArgumentConvertingMethodInvoker(); methodInvoker.setTargetClass(TestClass1.class); methodInvoker.setTargetMethod("intArgument"); - methodInvoker.setArguments(new Object[] {"5"}); + methodInvoker.setArguments(5); methodInvoker.prepare(); methodInvoker.invoke(); } @@ -309,37 +309,37 @@ public void testInvokeWithIntArguments() throws Exception { MethodInvokingBean methodInvoker = new MethodInvokingBean(); methodInvoker.setTargetClass(TestClass1.class); methodInvoker.setTargetMethod("intArguments"); - methodInvoker.setArguments(new Object[]{new Integer[] {5, 10}}); + methodInvoker.setArguments(new Object[] {new Integer[] {5, 10}}); methodInvoker.afterPropertiesSet(); methodInvoker = new MethodInvokingBean(); methodInvoker.setTargetClass(TestClass1.class); methodInvoker.setTargetMethod("intArguments"); - methodInvoker.setArguments(new Object[]{new String[]{"5", "10"}}); + methodInvoker.setArguments(new Object[] {new String[] {"5", "10"}}); methodInvoker.afterPropertiesSet(); methodInvoker = new MethodInvokingBean(); methodInvoker.setTargetClass(TestClass1.class); methodInvoker.setTargetMethod("intArguments"); - methodInvoker.setArguments(new Object[]{new Integer[] {5, 10}}); + methodInvoker.setArguments(new Object[] {new Integer[] {5, 10}}); methodInvoker.afterPropertiesSet(); methodInvoker = new MethodInvokingBean(); methodInvoker.setTargetClass(TestClass1.class); methodInvoker.setTargetMethod("intArguments"); - methodInvoker.setArguments(new String[]{"5", "10"}); + methodInvoker.setArguments("5", "10"); methodInvoker.afterPropertiesSet(); methodInvoker = new MethodInvokingBean(); methodInvoker.setTargetClass(TestClass1.class); methodInvoker.setTargetMethod("intArguments"); - methodInvoker.setArguments(new Object[]{new Integer[] {5, 10}}); + methodInvoker.setArguments(new Object[] {new Integer[] {5, 10}}); methodInvoker.afterPropertiesSet(); methodInvoker = new MethodInvokingBean(); methodInvoker.setTargetClass(TestClass1.class); methodInvoker.setTargetMethod("intArguments"); - methodInvoker.setArguments(new Object[]{"5", "10"}); + methodInvoker.setArguments("5", "10"); methodInvoker.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 6cccbc6d0150..94d5971be445 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-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,7 @@ import org.springframework.core.io.Resource; import org.springframework.tests.sample.beans.IndexedTestBean; import org.springframework.tests.sample.beans.TestBean; +import org.springframework.util.StringUtils; import static org.junit.Assert.*; import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*; @@ -838,12 +839,12 @@ protected void removeNodeSpi() throws BackingStoreException { @Override protected String[] keysSpi() throws BackingStoreException { - return values.keySet().toArray(new String[values.size()]); + return StringUtils.toStringArray(values.keySet()); } @Override protected String[] childrenNamesSpi() throws BackingStoreException { - return children.keySet().toArray(new String[values.size()]); + return StringUtils.toStringArray(children.keySet()); } @Override diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlMapFactoryBeanTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlMapFactoryBeanTests.java index 313b547b4571..bd4f968c8f27 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlMapFactoryBeanTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlMapFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,43 +34,40 @@ * Tests for {@link YamlMapFactoryBean}. * * @author Dave Syer + * @author Juergen Hoeller */ public class YamlMapFactoryBeanTests { private final YamlMapFactoryBean factory = new YamlMapFactoryBean(); + @Test public void testSetIgnoreResourceNotFound() throws Exception { - this.factory - .setResolutionMethod(YamlMapFactoryBean.ResolutionMethod.OVERRIDE_AND_IGNORE); - this.factory.setResources(new FileSystemResource[] {new FileSystemResource( - "non-exsitent-file.yml")}); + this.factory.setResolutionMethod(YamlMapFactoryBean.ResolutionMethod.OVERRIDE_AND_IGNORE); + this.factory.setResources(new FileSystemResource("non-exsitent-file.yml")); assertEquals(0, this.factory.getObject().size()); } @Test(expected = IllegalStateException.class) public void testSetBarfOnResourceNotFound() throws Exception { - this.factory.setResources(new FileSystemResource[] {new FileSystemResource( - "non-exsitent-file.yml")}); + this.factory.setResources(new FileSystemResource("non-exsitent-file.yml")); assertEquals(0, this.factory.getObject().size()); } @Test public void testGetObject() throws Exception { - this.factory.setResources(new ByteArrayResource[] {new ByteArrayResource( - "foo: bar".getBytes())}); + this.factory.setResources(new ByteArrayResource("foo: bar".getBytes())); assertEquals(1, this.factory.getObject().size()); } @SuppressWarnings("unchecked") @Test - public void testOverrideAndremoveDefaults() throws Exception { - this.factory.setResources(new ByteArrayResource[] { - new ByteArrayResource("foo:\n bar: spam".getBytes()), - new ByteArrayResource("foo:\n spam: bar".getBytes())}); + public void testOverrideAndRemoveDefaults() throws Exception { + this.factory.setResources(new ByteArrayResource("foo:\n bar: spam".getBytes()), + new ByteArrayResource("foo:\n spam: bar".getBytes())); + assertEquals(1, this.factory.getObject().size()); - assertEquals(2, - ((Map) this.factory.getObject().get("foo")).size()); + assertEquals(2, ((Map) this.factory.getObject().get("foo")).size()); } @Test @@ -81,20 +78,20 @@ public void testFirstFound() throws Exception { public String getDescription() { return "non-existent"; } - @Override public InputStream getInputStream() throws IOException { throw new IOException("planned"); } }, new ByteArrayResource("foo:\n spam: bar".getBytes())); + assertEquals(1, this.factory.getObject().size()); } @Test public void testMapWithPeriodsInKey() throws Exception { - this.factory.setResources(new ByteArrayResource[] {new ByteArrayResource( - "foo:\n ? key1.key2\n : value".getBytes())}); + this.factory.setResources(new ByteArrayResource("foo:\n ? key1.key2\n : value".getBytes())); Map map = this.factory.getObject(); + assertEquals(1, map.size()); assertTrue(map.containsKey("foo")); Object object = map.get("foo"); @@ -105,10 +102,24 @@ public void testMapWithPeriodsInKey() throws Exception { assertEquals("value", sub.get("key1.key2")); } + @Test + public void testMapWithIntegerValue() throws Exception { + this.factory.setResources(new ByteArrayResource("foo:\n ? key1.key2\n : 3".getBytes())); + Map map = this.factory.getObject(); + + assertEquals(1, map.size()); + assertTrue(map.containsKey("foo")); + Object object = map.get("foo"); + assertTrue(object instanceof LinkedHashMap); + @SuppressWarnings("unchecked") + Map sub = (Map) object; + assertTrue(sub.containsKey("key1.key2")); + assertEquals(Integer.valueOf(3), sub.get("key1.key2")); + } + @Test(expected = ParserException.class) public void testDuplicateKey() throws Exception { - this.factory.setResources(new ByteArrayResource[] {new ByteArrayResource( - "mymap:\n foo: bar\nmymap:\n bar: foo".getBytes())}); + this.factory.setResources(new ByteArrayResource("mymap:\n foo: bar\nmymap:\n bar: foo".getBytes())); this.factory.getObject().get("mymap"); } 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 fd90fbeb6661..51740c741840 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-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.beans.factory.config; import java.util.LinkedHashMap; @@ -24,6 +25,7 @@ import org.junit.rules.ExpectedException; import org.yaml.snakeyaml.parser.ParserException; import org.yaml.snakeyaml.scanner.ScannerException; + import org.springframework.core.io.ByteArrayResource; import static org.junit.Assert.*; @@ -33,34 +35,38 @@ * Tests for {@link YamlProcessor}. * * @author Dave Syer + * @author Juergen Hoeller */ public class YamlProcessorTests { - private final YamlProcessor processor = new YamlProcessor() { - }; + private final YamlProcessor processor = new YamlProcessor() {}; @Rule public ExpectedException exception = ExpectedException.none(); + @Test public void arrayConvertedToIndexedBeanReference() { - this.processor.setResources(new ByteArrayResource( - "foo: bar\nbar: [1,2,3]".getBytes())); + this.processor.setResources(new ByteArrayResource("foo: bar\nbar: [1,2,3]".getBytes())); this.processor.process(new MatchCallback() { @Override public void process(Properties properties, Map map) { + assertEquals(4, properties.size()); + assertEquals("bar", properties.get("foo")); + assertEquals("bar", properties.getProperty("foo")); assertEquals(1, properties.get("bar[0]")); + assertEquals("1", properties.getProperty("bar[0]")); assertEquals(2, properties.get("bar[1]")); + assertEquals("2", properties.getProperty("bar[1]")); assertEquals(3, properties.get("bar[2]")); - assertEquals(4, properties.size()); + assertEquals("3", properties.getProperty("bar[2]")); } }); } @Test public void testStringResource() throws Exception { - this.processor.setResources(new ByteArrayResource( - "foo # a document that is a literal".getBytes())); + this.processor.setResources(new ByteArrayResource("foo # a document that is a literal".getBytes())); this.processor.process(new MatchCallback() { @Override public void process(Properties properties, Map map) { @@ -71,8 +77,7 @@ public void process(Properties properties, Map map) { @Test public void testBadDocumentStart() throws Exception { - this.processor.setResources(new ByteArrayResource( - "foo # a document\nbar: baz".getBytes())); + this.processor.setResources(new ByteArrayResource("foo # a document\nbar: baz".getBytes())); this.exception.expect(ParserException.class); this.exception.expectMessage("line 2, column 1"); this.processor.process(new MatchCallback() { @@ -84,8 +89,7 @@ public void process(Properties properties, Map map) { @Test public void testBadResource() throws Exception { - this.processor.setResources(new ByteArrayResource( - "foo: bar\ncd\nspam:\n foo: baz".getBytes())); + this.processor.setResources(new ByteArrayResource("foo: bar\ncd\nspam:\n foo: baz".getBytes())); this.exception.expect(ScannerException.class); this.exception.expectMessage("line 3, column 1"); this.processor.process(new MatchCallback() { @@ -97,8 +101,7 @@ public void process(Properties properties, Map map) { @Test public void mapConvertedToIndexedBeanReference() { - this.processor.setResources(new ByteArrayResource( - "foo: bar\nbar:\n spam: bucket".getBytes())); + this.processor.setResources(new ByteArrayResource("foo: bar\nbar:\n spam: bucket".getBytes())); this.processor.process(new MatchCallback() { @Override public void process(Properties properties, Map map) { @@ -111,8 +114,7 @@ public void process(Properties properties, Map map) { @Test public void integerKeyBehaves() { - this.processor.setResources(new ByteArrayResource( - "foo: bar\n1: bar".getBytes())); + this.processor.setResources(new ByteArrayResource("foo: bar\n1: bar".getBytes())); this.processor.process(new MatchCallback() { @Override public void process(Properties properties, Map map) { @@ -124,10 +126,8 @@ public void process(Properties properties, Map map) { @Test public void integerDeepKeyBehaves() { - this.processor.setResources(new ByteArrayResource( - "foo:\n 1: bar".getBytes())); + this.processor.setResources(new ByteArrayResource("foo:\n 1: bar".getBytes())); this.processor.process(new MatchCallback() { - @Override public void process(Properties properties, Map map) { assertEquals("bar", properties.get("foo[1]")); @@ -139,8 +139,7 @@ public void process(Properties properties, Map map) { @Test @SuppressWarnings("unchecked") public void flattenedMapIsSameAsPropertiesButOrdered() { - this.processor.setResources(new ByteArrayResource( - "foo: bar\nbar:\n spam: bucket".getBytes())); + this.processor.setResources(new ByteArrayResource("foo: bar\nbar:\n spam: bucket".getBytes())); this.processor.process(new MatchCallback() { @Override public void process(Properties properties, Map map) { @@ -155,4 +154,5 @@ public void process(Properties properties, Map map) { } }); } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBeanTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBeanTests.java index 855e479a6414..fe2ca4945f9f 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBeanTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,24 +38,25 @@ * Tests for {@link YamlPropertiesFactoryBean}. * * @author Dave Syer + * @author Juergen Hoeller */ public class YamlPropertiesFactoryBeanTests { @Rule public ExpectedException exception = ExpectedException.none(); + @Test - public void testLoadResource() throws Exception { + public void testLoadResource() { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); - factory.setResources(new ByteArrayResource( - "foo: bar\nspam:\n foo: baz".getBytes())); + factory.setResources(new ByteArrayResource("foo: bar\nspam:\n foo: baz".getBytes())); Properties properties = factory.getObject(); assertThat(properties.getProperty("foo"), equalTo("bar")); assertThat(properties.getProperty("spam.foo"), equalTo("baz")); } @Test - public void testBadResource() throws Exception { + public void testBadResource() { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); factory.setResources(new ByteArrayResource( "foo: bar\ncd\nspam:\n foo: baz".getBytes())); @@ -65,7 +66,7 @@ public void testBadResource() throws Exception { } @Test - public void testLoadResourcesWithOverride() throws Exception { + public void testLoadResourcesWithOverride() { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); factory.setResources( new ByteArrayResource("foo: bar\nspam:\n foo: baz".getBytes()), @@ -77,7 +78,7 @@ public void testLoadResourcesWithOverride() throws Exception { } @Test - public void testLoadResourcesWithInternalOverride() throws Exception { + public void testLoadResourcesWithInternalOverride() { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); factory.setResources(new ByteArrayResource( "foo: bar\nspam:\n foo: baz\nfoo: bucket".getBytes())); @@ -87,7 +88,7 @@ public void testLoadResourcesWithInternalOverride() throws Exception { @Test @Ignore("We can't fail on duplicate keys because the Map is created by the YAML library") - public void testLoadResourcesWithNestedInternalOverride() throws Exception { + public void testLoadResourcesWithNestedInternalOverride() { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); factory.setResources(new ByteArrayResource( "foo:\n bar: spam\n foo: baz\nbreak: it\nfoo: bucket".getBytes())); @@ -96,7 +97,7 @@ public void testLoadResourcesWithNestedInternalOverride() throws Exception { } @Test - public void testLoadResourceWithMultipleDocuments() throws Exception { + public void testLoadResourceWithMultipleDocuments() { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); factory.setResources(new ByteArrayResource( "foo: bar\nspam: baz\n---\nfoo: bag".getBytes())); @@ -106,37 +107,29 @@ public void testLoadResourceWithMultipleDocuments() throws Exception { } @Test - public void testLoadResourceWithSelectedDocuments() throws Exception { + public void testLoadResourceWithSelectedDocuments() { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); factory.setResources(new ByteArrayResource( "foo: bar\nspam: baz\n---\nfoo: bag\nspam: bad".getBytes())); - factory.setDocumentMatchers(new DocumentMatcher() { - @Override - public MatchStatus matches(Properties properties) { - return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND - : MatchStatus.NOT_FOUND; - } - }); + factory.setDocumentMatchers(properties -> ("bag".equals(properties.getProperty("foo")) ? + MatchStatus.FOUND : MatchStatus.NOT_FOUND)); Properties properties = factory.getObject(); assertThat(properties.getProperty("foo"), equalTo("bag")); assertThat(properties.getProperty("spam"), equalTo("bad")); } @Test - public void testLoadResourceWithDefaultMatch() throws Exception { + public void testLoadResourceWithDefaultMatch() { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); factory.setMatchDefault(true); factory.setResources(new ByteArrayResource( "one: two\n---\nfoo: bar\nspam: baz\n---\nfoo: bag\nspam: bad".getBytes())); - factory.setDocumentMatchers(new DocumentMatcher() { - @Override - public MatchStatus matches(Properties properties) { - if (!properties.containsKey("foo")) { - return MatchStatus.ABSTAIN; - } - return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND - : MatchStatus.NOT_FOUND; + factory.setDocumentMatchers(properties -> { + if (!properties.containsKey("foo")) { + return MatchStatus.ABSTAIN; } + return ("bag".equals(properties.getProperty("foo")) ? + MatchStatus.FOUND : MatchStatus.NOT_FOUND); }); Properties properties = factory.getObject(); assertThat(properties.getProperty("foo"), equalTo("bag")); @@ -145,7 +138,7 @@ public MatchStatus matches(Properties properties) { } @Test - public void testLoadResourceWithoutDefaultMatch() throws Exception { + public void testLoadResourceWithoutDefaultMatch() { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); factory.setMatchDefault(false); factory.setResources(new ByteArrayResource( @@ -156,8 +149,8 @@ public MatchStatus matches(Properties properties) { if (!properties.containsKey("foo")) { return MatchStatus.ABSTAIN; } - return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND - : MatchStatus.NOT_FOUND; + return ("bag".equals(properties.getProperty("foo")) ? + MatchStatus.FOUND : MatchStatus.NOT_FOUND); } }); Properties properties = factory.getObject(); @@ -167,20 +160,17 @@ public MatchStatus matches(Properties properties) { } @Test - public void testLoadResourceWithDefaultMatchSkippingMissedMatch() throws Exception { + public void testLoadResourceWithDefaultMatchSkippingMissedMatch() { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); factory.setMatchDefault(true); factory.setResources(new ByteArrayResource( "one: two\n---\nfoo: bag\nspam: bad\n---\nfoo: bar\nspam: baz".getBytes())); - factory.setDocumentMatchers(new DocumentMatcher() { - @Override - public MatchStatus matches(Properties properties) { - if (!properties.containsKey("foo")) { - return MatchStatus.ABSTAIN; - } - return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND - : MatchStatus.NOT_FOUND; + factory.setDocumentMatchers(properties -> { + if (!properties.containsKey("foo")) { + return MatchStatus.ABSTAIN; } + return ("bag".equals(properties.getProperty("foo")) ? + MatchStatus.FOUND : MatchStatus.NOT_FOUND); }); Properties properties = factory.getObject(); assertThat(properties.getProperty("foo"), equalTo("bag")); @@ -189,7 +179,7 @@ public MatchStatus matches(Properties properties) { } @Test - public void testLoadNonExistentResource() throws Exception { + public void testLoadNonExistentResource() { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); factory.setResolutionMethod(ResolutionMethod.OVERRIDE_AND_IGNORE); factory.setResources(new ClassPathResource("no-such-file.yml")); @@ -198,20 +188,18 @@ public void testLoadNonExistentResource() throws Exception { } @Test - public void testLoadNull() throws Exception { + public void testLoadNull() { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); - factory.setResources(new ByteArrayResource("foo: bar\nspam:" - .getBytes())); + factory.setResources(new ByteArrayResource("foo: bar\nspam:".getBytes())); Properties properties = factory.getObject(); assertThat(properties.getProperty("foo"), equalTo("bar")); assertThat(properties.getProperty("spam"), equalTo("")); } @Test - public void testLoadArrayOfString() throws Exception { + public void testLoadArrayOfString() { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); - factory.setResources(new ByteArrayResource("foo:\n- bar\n- baz" - .getBytes())); + factory.setResources(new ByteArrayResource("foo:\n- bar\n- baz".getBytes())); Properties properties = factory.getObject(); assertThat(properties.getProperty("foo[0]"), equalTo("bar")); assertThat(properties.getProperty("foo[1]"), equalTo("baz")); @@ -219,11 +207,20 @@ public void testLoadArrayOfString() throws Exception { } @Test - public void testLoadArrayOfObject() throws Exception { + public void testLoadArrayOfInteger() { + YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); + factory.setResources(new ByteArrayResource("foo:\n- 1\n- 2".getBytes())); + Properties properties = factory.getObject(); + assertThat(properties.getProperty("foo[0]"), equalTo("1")); + assertThat(properties.getProperty("foo[1]"), equalTo("2")); + assertThat(properties.get("foo"), is(nullValue())); + } + + @Test + public void testLoadArrayOfObject() { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); factory.setResources(new ByteArrayResource( - "foo:\n- bar:\n spam: crap\n- baz\n- one: two\n three: four" - .getBytes() + "foo:\n- bar:\n spam: crap\n- baz\n- one: two\n three: four".getBytes() )); Properties properties = factory.getObject(); assertThat(properties.getProperty("foo[0].bar.spam"), equalTo("crap")); @@ -238,9 +235,8 @@ public void testLoadArrayOfObject() throws Exception { public void testYaml() { Yaml yaml = new Yaml(); Map map = yaml.loadAs("foo: bar\nspam:\n foo: baz", Map.class); - assertThat(map.get("foo"), equalTo((Object) "bar")); - assertThat(((Map) map.get("spam")).get("foo"), - equalTo((Object) "baz")); + assertThat(map.get("foo"), equalTo("bar")); + assertThat(((Map) map.get("spam")).get("foo"), equalTo("baz")); } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/parsing/CustomProblemReporterTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/parsing/CustomProblemReporterTests.java index 17455d785cea..adfe6176cb12 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/parsing/CustomProblemReporterTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/parsing/CustomProblemReporterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,7 @@ * @author Chris Beams * @since 2.0 */ -public final class CustomProblemReporterTests { +public class CustomProblemReporterTests { private static final Resource CONTEXT = qualifiedResource(CustomProblemReporterTests.class, "context.xml"); @@ -66,10 +66,9 @@ public void testErrorsAreCollated() { private static class CollatingProblemReporter implements ProblemReporter { - private List errors = new ArrayList(); - - private List warnings = new ArrayList(); + private final List errors = new ArrayList<>(); + private final List warnings = new ArrayList<>(); @Override public void fatal(Problem problem) { diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/AutowireUtilsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/AutowireUtilsTests.java index 56fcde6ec9cf..c1de073dc27f 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/AutowireUtilsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/AutowireUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,60 +34,54 @@ public class AutowireUtilsTests { @Test public void genericMethodReturnTypes() { - Method notParameterized = ReflectionUtils.findMethod(MyTypeWithMethods.class, "notParameterized", new Class[]{}); + Method notParameterized = ReflectionUtils.findMethod(MyTypeWithMethods.class, "notParameterized"); assertEquals(String.class, AutowireUtils.resolveReturnTypeForFactoryMethod(notParameterized, new Object[]{}, getClass().getClassLoader())); - Method notParameterizedWithArguments = ReflectionUtils.findMethod(MyTypeWithMethods.class, "notParameterizedWithArguments", - new Class[] { Integer.class, Boolean.class }); + Method notParameterizedWithArguments = ReflectionUtils.findMethod(MyTypeWithMethods.class, "notParameterizedWithArguments", Integer.class, Boolean.class); assertEquals(String.class, - AutowireUtils.resolveReturnTypeForFactoryMethod(notParameterizedWithArguments, new Object[] { 99, true }, getClass().getClassLoader())); + AutowireUtils.resolveReturnTypeForFactoryMethod(notParameterizedWithArguments, new Object[] {99, true}, getClass().getClassLoader())); - Method createProxy = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createProxy", new Class[] { Object.class }); + Method createProxy = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createProxy", Object.class); assertEquals(String.class, - AutowireUtils.resolveReturnTypeForFactoryMethod(createProxy, new Object[] { "foo" }, getClass().getClassLoader())); + AutowireUtils.resolveReturnTypeForFactoryMethod(createProxy, new Object[] {"foo"}, getClass().getClassLoader())); - Method createNamedProxyWithDifferentTypes = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedProxy", - new Class[] { String.class, Object.class }); + Method createNamedProxyWithDifferentTypes = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedProxy", String.class, Object.class); assertEquals(Long.class, - AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDifferentTypes, new Object[] { "enigma", 99L }, getClass().getClassLoader())); + AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDifferentTypes, new Object[] {"enigma", 99L}, getClass().getClassLoader())); - Method createNamedProxyWithDuplicateTypes = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedProxy", - new Class[] { String.class, Object.class }); + Method createNamedProxyWithDuplicateTypes = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedProxy", String.class, Object.class); assertEquals(String.class, - AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDuplicateTypes, new Object[] { "enigma", "foo" }, getClass().getClassLoader())); + AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDuplicateTypes, new Object[] {"enigma", "foo"}, getClass().getClassLoader())); - Method createMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createMock", new Class[] { Class.class }); + Method createMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createMock", Class.class); assertEquals(Runnable.class, - AutowireUtils.resolveReturnTypeForFactoryMethod(createMock, new Object[] { Runnable.class }, getClass().getClassLoader())); + AutowireUtils.resolveReturnTypeForFactoryMethod(createMock, new Object[] {Runnable.class}, getClass().getClassLoader())); assertEquals(Runnable.class, - AutowireUtils.resolveReturnTypeForFactoryMethod(createMock, new Object[] { Runnable.class.getName() }, getClass().getClassLoader())); + AutowireUtils.resolveReturnTypeForFactoryMethod(createMock, new Object[] {Runnable.class.getName()}, getClass().getClassLoader())); - Method createNamedMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedMock", new Class[] { String.class, - Class.class }); + Method createNamedMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedMock", String.class, Class.class); assertEquals(Runnable.class, - AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedMock, new Object[] { "foo", Runnable.class }, getClass().getClassLoader())); + AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedMock, new Object[] {"foo", Runnable.class}, getClass().getClassLoader())); - Method createVMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createVMock", - new Class[] { Object.class, Class.class }); + Method createVMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createVMock", Object.class, Class.class); assertEquals(Runnable.class, - AutowireUtils.resolveReturnTypeForFactoryMethod(createVMock, new Object[] { "foo", Runnable.class }, getClass().getClassLoader())); + AutowireUtils.resolveReturnTypeForFactoryMethod(createVMock, new Object[] {"foo", Runnable.class}, getClass().getClassLoader())); // Ideally we would expect String.class instead of Object.class, but // resolveReturnTypeForFactoryMethod() does not currently support this form of // look-up. - Method extractValueFrom = ReflectionUtils.findMethod(MyTypeWithMethods.class, "extractValueFrom", - new Class[] { MyInterfaceType.class }); + Method extractValueFrom = ReflectionUtils.findMethod(MyTypeWithMethods.class, "extractValueFrom", MyInterfaceType.class); assertEquals(Object.class, - AutowireUtils.resolveReturnTypeForFactoryMethod(extractValueFrom, new Object[] { new MySimpleInterfaceType() }, getClass().getClassLoader())); + AutowireUtils.resolveReturnTypeForFactoryMethod(extractValueFrom, new Object[] {new MySimpleInterfaceType()}, getClass().getClassLoader())); // Ideally we would expect Boolean.class instead of Object.class, but this // information is not available at run-time due to type erasure. - Map map = new HashMap(); + Map map = new HashMap<>(); map.put(0, false); map.put(1, true); - Method extractMagicValue = ReflectionUtils.findMethod(MyTypeWithMethods.class, "extractMagicValue", new Class[] { Map.class }); - assertEquals(Object.class, AutowireUtils.resolveReturnTypeForFactoryMethod(extractMagicValue, new Object[] { map }, getClass().getClassLoader())); + Method extractMagicValue = ReflectionUtils.findMethod(MyTypeWithMethods.class, "extractMagicValue", Map.class); + assertEquals(Object.class, AutowireUtils.resolveReturnTypeForFactoryMethod(extractMagicValue, new Object[] {map}, getClass().getClassLoader())); } 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 899bee055260..9e32012468aa 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-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -126,6 +126,7 @@ public void beanDefinitionMerging() { bd.getConstructorArgumentValues().addIndexedArgumentValue(1, new Integer(5)); bd.getPropertyValues().add("name", "myName"); bd.getPropertyValues().add("age", "99"); + bd.setQualifiedElement(getClass()); GenericBeanDefinition childBd = new GenericBeanDefinition(); childBd.setParentName("bd"); @@ -138,6 +139,7 @@ public void beanDefinitionMerging() { mergedBd.getConstructorArgumentValues().getArgumentValue(1, null).setValue(new Integer(9)); assertEquals(new Integer(5), bd.getConstructorArgumentValues().getArgumentValue(1, null).getValue()); + assertEquals(getClass(), bd.getQualifiedElement()); } } 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 ed1b506b7a79..4a0fcec07168 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-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,6 +40,7 @@ import org.springframework.beans.factory.config.TypedStringValue; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.beans.propertyeditors.CustomNumberEditor; +import org.springframework.core.OverridingClassLoader; import org.springframework.core.ResolvableType; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.UrlResource; @@ -65,7 +66,7 @@ public void testGenericSetProperty() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - Set input = new HashSet(); + Set input = new HashSet<>(); input.add("4"); input.add("5"); rbd.getPropertyValues().add("integerSet", input); @@ -82,7 +83,7 @@ public void testGenericListProperty() throws MalformedURLException { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - List input = new ArrayList(); + List input = new ArrayList<>(); input.add("http://localhost:8080"); input.add("http://localhost:9090"); rbd.getPropertyValues().add("resourceList", input); @@ -114,7 +115,7 @@ public void testGenericListPropertyWithInvalidElementType() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericIntegerBean.class); - List input = new ArrayList(); + List input = new ArrayList<>(); input.add(1); rbd.getPropertyValues().add("testBeanList", input); @@ -146,7 +147,7 @@ public void testGenericMapProperty() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - Map input = new HashMap(); + Map input = new HashMap<>(); input.put("4", "5"); input.put("6", "7"); rbd.getPropertyValues().add("shortMap", input); @@ -178,7 +179,7 @@ public void testGenericSetConstructor() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - Set input = new HashSet(); + Set input = new HashSet<>(); input.add("4"); input.add("5"); rbd.getConstructorArgumentValues().addGenericArgumentValue(input); @@ -222,10 +223,10 @@ public void testGenericSetListConstructor() throws MalformedURLException { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - Set input = new HashSet(); + Set input = new HashSet<>(); input.add("4"); input.add("5"); - List input2 = new ArrayList(); + List input2 = new ArrayList<>(); input2.add("http://localhost:8080"); input2.add("http://localhost:9090"); rbd.getConstructorArgumentValues().addGenericArgumentValue(input); @@ -279,10 +280,10 @@ public void testGenericSetMapConstructor() throws MalformedURLException { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - Set input = new HashSet(); + Set input = new HashSet<>(); input.add("4"); input.add("5"); - Map input2 = new HashMap(); + Map input2 = new HashMap<>(); input2.put("4", "5"); input2.put("6", "7"); rbd.getConstructorArgumentValues().addGenericArgumentValue(input); @@ -302,7 +303,7 @@ public void testGenericMapResourceConstructor() throws MalformedURLException { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - Map input = new HashMap(); + Map input = new HashMap<>(); input.put("4", "5"); input.put("6", "7"); rbd.getConstructorArgumentValues().addGenericArgumentValue(input); @@ -321,10 +322,10 @@ public void testGenericMapMapConstructor() throws MalformedURLException { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - Map input = new HashMap(); + Map input = new HashMap<>(); input.put("1", "0"); input.put("2", "3"); - Map input2 = new HashMap(); + Map input2 = new HashMap<>(); input2.put("4", "5"); input2.put("6", "7"); rbd.getConstructorArgumentValues().addGenericArgumentValue(input); @@ -347,7 +348,7 @@ public void testGenericMapMapConstructorWithSameRefAndConversion() throws Malfor DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - Map input = new HashMap(); + Map input = new HashMap<>(); input.put("1", "0"); input.put("2", "3"); rbd.getConstructorArgumentValues().addGenericArgumentValue(input); @@ -370,7 +371,7 @@ public void testGenericMapMapConstructorWithSameRefAndNoConversion() throws Malf DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - Map input = new HashMap(); + Map input = new HashMap<>(); input.put(new Short((short) 1), new Integer(0)); input.put(new Short((short) 2), new Integer(3)); rbd.getConstructorArgumentValues().addGenericArgumentValue(input); @@ -390,7 +391,7 @@ public void testGenericMapWithKeyTypeConstructor() throws MalformedURLException DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - Map input = new HashMap(); + Map input = new HashMap<>(); input.put("4", "5"); input.put("6", "7"); rbd.getConstructorArgumentValues().addGenericArgumentValue(input); @@ -413,11 +414,11 @@ public void registerCustomEditors(PropertyEditorRegistry registry) { }); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - Map> input = new HashMap>(); - HashSet value1 = new HashSet(); + Map> input = new HashMap<>(); + HashSet value1 = new HashSet<>(); value1.add(new Integer(1)); input.put("1", value1); - ArrayList value2 = new ArrayList(); + ArrayList value2 = new ArrayList<>(); value2.add(Boolean.TRUE); input.put("2", value2); rbd.getConstructorArgumentValues().addGenericArgumentValue(Boolean.TRUE); @@ -437,7 +438,7 @@ public void testGenericSetFactoryMethod() { RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); rbd.setFactoryMethodName("createInstance"); - Set input = new HashSet(); + Set input = new HashSet<>(); input.add("4"); input.add("5"); rbd.getConstructorArgumentValues().addGenericArgumentValue(input); @@ -455,10 +456,10 @@ public void testGenericSetListFactoryMethod() throws MalformedURLException { RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); rbd.setFactoryMethodName("createInstance"); - Set input = new HashSet(); + Set input = new HashSet<>(); input.add("4"); input.add("5"); - List input2 = new ArrayList(); + List input2 = new ArrayList<>(); input2.add("http://localhost:8080"); input2.add("http://localhost:9090"); rbd.getConstructorArgumentValues().addGenericArgumentValue(input); @@ -479,10 +480,10 @@ public void testGenericSetMapFactoryMethod() throws MalformedURLException { RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); rbd.setFactoryMethodName("createInstance"); - Set input = new HashSet(); + Set input = new HashSet<>(); input.add("4"); input.add("5"); - Map input2 = new HashMap(); + Map input2 = new HashMap<>(); input2.put("4", "5"); input2.put("6", "7"); rbd.getConstructorArgumentValues().addGenericArgumentValue(input); @@ -503,7 +504,7 @@ public void testGenericMapResourceFactoryMethod() throws MalformedURLException { RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); rbd.setFactoryMethodName("createInstance"); - Map input = new HashMap(); + Map input = new HashMap<>(); input.put("4", "5"); input.put("6", "7"); rbd.getConstructorArgumentValues().addGenericArgumentValue(input); @@ -523,10 +524,10 @@ public void testGenericMapMapFactoryMethod() throws MalformedURLException { RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); rbd.setFactoryMethodName("createInstance"); - Map input = new HashMap(); + Map input = new HashMap<>(); input.put("1", "0"); input.put("2", "3"); - Map input2 = new HashMap(); + Map input2 = new HashMap<>(); input2.put("4", "5"); input2.put("6", "7"); rbd.getConstructorArgumentValues().addGenericArgumentValue(input); @@ -547,7 +548,7 @@ public void testGenericMapWithKeyTypeFactoryMethod() throws MalformedURLExceptio RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); rbd.setFactoryMethodName("createInstance"); - Map input = new HashMap(); + Map input = new HashMap<>(); input.put("4", "5"); input.put("6", "7"); rbd.getConstructorArgumentValues().addGenericArgumentValue(input); @@ -571,11 +572,11 @@ public void registerCustomEditors(PropertyEditorRegistry registry) { RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); rbd.setFactoryMethodName("createInstance"); - Map> input = new HashMap>(); - HashSet value1 = new HashSet(); + Map> input = new HashMap<>(); + HashSet value1 = new HashSet<>(); value1.add(new Integer(1)); input.put("1", value1); - ArrayList value2 = new ArrayList(); + ArrayList value2 = new ArrayList<>(); value2.add(Boolean.TRUE); input.put("2", value2); rbd.getConstructorArgumentValues().addGenericArgumentValue(Boolean.TRUE); @@ -672,6 +673,8 @@ public void parameterizedStaticFactoryMethod() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); bf.registerBeanDefinition("mock", rbd); + assertEquals(Runnable.class, bf.getType("mock")); + assertEquals(Runnable.class, bf.getType("mock")); Map beans = bf.getBeansOfType(Runnable.class); assertEquals(1, beans.size()); } @@ -700,6 +703,10 @@ public void parameterizedInstanceFactoryMethod() { rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class); bf.registerBeanDefinition("mock", rbd); + assertTrue(bf.isTypeMatch("mock", Runnable.class)); + assertTrue(bf.isTypeMatch("mock", Runnable.class)); + assertEquals(Runnable.class, bf.getType("mock")); + assertEquals(Runnable.class, bf.getType("mock")); Map beans = bf.getBeansOfType(Runnable.class); assertEquals(1, beans.size()); } @@ -717,6 +724,10 @@ public void parameterizedInstanceFactoryMethodWithNonResolvedClassName() { rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class.getName()); bf.registerBeanDefinition("mock", rbd); + assertTrue(bf.isTypeMatch("mock", Runnable.class)); + assertTrue(bf.isTypeMatch("mock", Runnable.class)); + assertEquals(Runnable.class, bf.getType("mock")); + assertEquals(Runnable.class, bf.getType("mock")); Map beans = bf.getBeansOfType(Runnable.class); assertEquals(1, beans.size()); } @@ -732,6 +743,10 @@ public void parameterizedInstanceFactoryMethodWithWrappedClassName() { rbd.getConstructorArgumentValues().addGenericArgumentValue(new TypedStringValue(Runnable.class.getName())); bf.registerBeanDefinition("mock", rbd); + assertTrue(bf.isTypeMatch("mock", Runnable.class)); + assertTrue(bf.isTypeMatch("mock", Runnable.class)); + assertEquals(Runnable.class, bf.getType("mock")); + assertEquals(Runnable.class, bf.getType("mock")); Map beans = bf.getBeansOfType(Runnable.class); assertEquals(1, beans.size()); } @@ -749,6 +764,10 @@ public void parameterizedInstanceFactoryMethodWithInvalidClassName() { rbd.getConstructorArgumentValues().addGenericArgumentValue("x"); bf.registerBeanDefinition("mock", rbd); + assertFalse(bf.isTypeMatch("mock", Runnable.class)); + assertFalse(bf.isTypeMatch("mock", Runnable.class)); + assertNull(bf.getType("mock")); + assertNull(bf.getType("mock")); Map beans = bf.getBeansOfType(Runnable.class); assertEquals(0, beans.size()); } @@ -766,6 +785,32 @@ public void parameterizedInstanceFactoryMethodWithIndexedArgument() { rbd.getConstructorArgumentValues().addIndexedArgumentValue(0, Runnable.class); bf.registerBeanDefinition("mock", rbd); + assertTrue(bf.isTypeMatch("mock", Runnable.class)); + assertTrue(bf.isTypeMatch("mock", Runnable.class)); + assertEquals(Runnable.class, bf.getType("mock")); + assertEquals(Runnable.class, bf.getType("mock")); + Map beans = bf.getBeansOfType(Runnable.class); + assertEquals(1, beans.size()); + } + + @Test // SPR-16720 + public void parameterizedInstanceFactoryMethodWithTempClassLoader() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setTempClassLoader(new OverridingClassLoader(getClass().getClassLoader())); + + RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); + bf.registerBeanDefinition("mocksControl", rbd); + + rbd = new RootBeanDefinition(); + rbd.setFactoryBeanName("mocksControl"); + rbd.setFactoryMethodName("createMock"); + rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class); + bf.registerBeanDefinition("mock", rbd); + + assertTrue(bf.isTypeMatch("mock", Runnable.class)); + assertTrue(bf.isTypeMatch("mock", Runnable.class)); + assertEquals(Runnable.class, bf.getType("mock")); + assertEquals(Runnable.class, bf.getType("mock")); Map beans = bf.getBeansOfType(Runnable.class); assertEquals(1, beans.size()); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/xml/ProfileXmlBeanDefinitionTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/xml/ProfileXmlBeanDefinitionTests.java index 286b4062ac3e..faa9ca0bbdd3 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/xml/ProfileXmlBeanDefinitionTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/xml/ProfileXmlBeanDefinitionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,6 +45,7 @@ public class ProfileXmlBeanDefinitionTests { private static final String NOT_DEV_ELIGIBLE_XML = "ProfileXmlBeanDefinitionTests-notDevProfile.xml"; private static final String ALL_ELIGIBLE_XML = "ProfileXmlBeanDefinitionTests-noProfile.xml"; private static final String MULTI_ELIGIBLE_XML = "ProfileXmlBeanDefinitionTests-multiProfile.xml"; + private static final String MULTI_NEGATED_XML = "ProfileXmlBeanDefinitionTests-multiProfileNegated.xml"; private static final String MULTI_NOT_DEV_ELIGIBLE_XML = "ProfileXmlBeanDefinitionTests-multiProfileNotDev.xml"; private static final String MULTI_ELIGIBLE_SPACE_DELIMITED_XML = "ProfileXmlBeanDefinitionTests-spaceDelimitedProfile.xml"; private static final String UNKNOWN_ELIGIBLE_XML = "ProfileXmlBeanDefinitionTests-unknownProfile.xml"; @@ -61,7 +62,7 @@ public class ProfileXmlBeanDefinitionTests { private static final String TARGET_BEAN = "foo"; - @Test(expected=IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void testProfileValidation() { beanFactoryFor(PROD_ELIGIBLE_XML, NULL_ACTIVE); } @@ -94,6 +95,12 @@ public void testProfilePermutations() { assertThat(beanFactoryFor(MULTI_ELIGIBLE_XML, PROD_ACTIVE), containsTargetBean()); assertThat(beanFactoryFor(MULTI_ELIGIBLE_XML, MULTI_ACTIVE), containsTargetBean()); + assertThat(beanFactoryFor(MULTI_NEGATED_XML, NONE_ACTIVE), containsTargetBean()); + assertThat(beanFactoryFor(MULTI_NEGATED_XML, UNKNOWN_ACTIVE), containsTargetBean()); + assertThat(beanFactoryFor(MULTI_NEGATED_XML, DEV_ACTIVE), containsTargetBean()); + assertThat(beanFactoryFor(MULTI_NEGATED_XML, PROD_ACTIVE), containsTargetBean()); + assertThat(beanFactoryFor(MULTI_NEGATED_XML, MULTI_ACTIVE), not(containsTargetBean())); + assertThat(beanFactoryFor(MULTI_NOT_DEV_ELIGIBLE_XML, NONE_ACTIVE), containsTargetBean()); assertThat(beanFactoryFor(MULTI_NOT_DEV_ELIGIBLE_XML, UNKNOWN_ACTIVE), containsTargetBean()); assertThat(beanFactoryFor(MULTI_NOT_DEV_ELIGIBLE_XML, DEV_ACTIVE), not(containsTargetBean())); 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 25848af501fd..15b8ad914a65 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-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,30 +42,33 @@ * @author Juergen Hoeller * @since 09.11.2003 */ -@SuppressWarnings({ "rawtypes", "unchecked" }) +@SuppressWarnings({"rawtypes", "unchecked"}) public class XmlListableBeanFactoryTests extends AbstractListableBeanFactoryTests { private DefaultListableBeanFactory parent; private DefaultListableBeanFactory factory; + @Before - public void setUp() { + public void setup() { parent = new DefaultListableBeanFactory(); - Map m = new HashMap(); - m.put("name", "Albert"); + + Map map = new HashMap(); + map.put("name", "Albert"); RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class); - bd1.setPropertyValues(new MutablePropertyValues(m)); + bd1.setPropertyValues(new MutablePropertyValues(map)); parent.registerBeanDefinition("father", bd1); - m = new HashMap(); - m.put("name", "Roderick"); + + map = new HashMap(); + map.put("name", "Roderick"); RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class); - bd2.setPropertyValues(new MutablePropertyValues(m)); + bd2.setPropertyValues(new MutablePropertyValues(map)); parent.registerBeanDefinition("rod", bd2); this.factory = new DefaultListableBeanFactory(parent); - new XmlBeanDefinitionReader(this.factory).loadBeanDefinitions( - new ClassPathResource("test.xml", getClass())); + new XmlBeanDefinitionReader(this.factory).loadBeanDefinitions(new ClassPathResource("test.xml", getClass())); + this.factory.addBeanPostProcessor(new BeanPostProcessor() { @Override public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException { @@ -82,9 +85,10 @@ public Object postProcessAfterInitialization(Object bean, String name) throws Be return bean; } }); + this.factory.addBeanPostProcessor(new LifecycleBean.PostProcessor()); this.factory.addBeanPostProcessor(new ProtectedLifecycleBean.PostProcessor()); - //this.factory.preInstantiateSingletons(); + // this.factory.preInstantiateSingletons(); } @Override @@ -92,6 +96,7 @@ protected BeanFactory getBeanFactory() { return factory; } + @Test @Override public void count() { @@ -104,19 +109,19 @@ public void beanCount() { } @Test - public void lifecycleMethods() throws Exception { + public void lifecycleMethods() { LifecycleBean bean = (LifecycleBean) getBeanFactory().getBean("lifecycle"); bean.businessMethod(); } @Test - public void protectedLifecycleMethods() throws Exception { + public void protectedLifecycleMethods() { ProtectedLifecycleBean bean = (ProtectedLifecycleBean) getBeanFactory().getBean("protectedLifecycle"); bean.businessMethod(); } @Test - public void descriptionButNoProperties() throws Exception { + public void descriptionButNoProperties() { TestBean validEmpty = (TestBean) getBeanFactory().getBean("validEmptyWithDescription"); assertEquals(0, validEmpty.getAge()); } @@ -125,7 +130,7 @@ public void descriptionButNoProperties() throws Exception { * Test that properties with name as well as id creating an alias up front. */ @Test - public void autoAliasing() throws Exception { + public void autoAliasing() { List beanNames = Arrays.asList(getListableBeanFactory().getBeanDefinitionNames()); TestBean tb1 = (TestBean) getBeanFactory().getBean("aliased"); @@ -224,7 +229,7 @@ public void prototypeReferences() { } @Test - public void beanPostProcessor() throws Exception { + public void beanPostProcessor() { TestBean kerry = (TestBean) getBeanFactory().getBean("kerry"); TestBean kathy = (TestBean) getBeanFactory().getBean("kathy"); DummyFactory factory = (DummyFactory) getBeanFactory().getBean("&singletonFactory"); diff --git a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/FileEditorTests.java b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/FileEditorTests.java index ae6a5c6838aa..ab76a6498f06 100644 --- a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/FileEditorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/FileEditorTests.java @@ -28,6 +28,7 @@ /** * @author Thomas Risberg * @author Chris Beams + * @author Juergen Hoeller */ public class FileEditorTests { @@ -78,10 +79,7 @@ public void testUnqualifiedFileNameFound() throws Exception { assertTrue(value instanceof File); File file = (File) value; assertTrue(file.exists()); - String absolutePath = file.getAbsolutePath(); - if (File.separatorChar == '\\') { - absolutePath = absolutePath.replace('\\', '/'); - } + String absolutePath = file.getAbsolutePath().replace('\\', '/'); assertTrue(absolutePath.endsWith(fileName)); } @@ -95,10 +93,7 @@ public void testUnqualifiedFileNameNotFound() throws Exception { assertTrue(value instanceof File); File file = (File) value; assertFalse(file.exists()); - String absolutePath = file.getAbsolutePath(); - if (File.separatorChar == '\\') { - absolutePath = absolutePath.replace('\\', '/'); - } + String absolutePath = file.getAbsolutePath().replace('\\', '/'); assertTrue(absolutePath.endsWith(fileName)); } 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 440a3e38c737..e96bd94a8495 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 @@ -59,6 +59,16 @@ public void testWithNonExistentPath() throws Exception { assertTrue(!path.toFile().exists()); } + @Test + public void testAbsolutePath() throws Exception { + PropertyEditor pathEditor = new PathEditor(); + pathEditor.setAsText("/no_way_this_file_is_found.doc"); + Object value = pathEditor.getValue(); + assertTrue(value instanceof Path); + Path path = (Path) value; + assertTrue(!path.toFile().exists()); + } + @Test public void testUnqualifiedPathNameFound() throws Exception { PropertyEditor pathEditor = new PathEditor(); @@ -77,4 +87,22 @@ public void testUnqualifiedPathNameFound() throws Exception { assertTrue(absolutePath.endsWith(fileName)); } + @Test + public void testUnqualifiedPathNameNotFound() throws Exception { + PropertyEditor pathEditor = new PathEditor(); + String fileName = ClassUtils.classPackageAsResourcePath(getClass()) + "/" + + ClassUtils.getShortName(getClass()) + ".clazz"; + pathEditor.setAsText(fileName); + Object value = pathEditor.getValue(); + assertTrue(value instanceof Path); + Path path = (Path) value; + File file = path.toFile(); + assertFalse(file.exists()); + String absolutePath = file.getAbsolutePath(); + if (File.separatorChar == '\\') { + absolutePath = absolutePath.replace('\\', '/'); + } + assertTrue(absolutePath.endsWith(fileName)); + } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/support/PropertyComparatorTests.java b/spring-beans/src/test/java/org/springframework/beans/support/PropertyComparatorTests.java index 09d74c73f4b1..bc1687167085 100644 --- a/spring-beans/src/test/java/org/springframework/beans/support/PropertyComparatorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/support/PropertyComparatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,9 +23,7 @@ import static org.junit.Assert.*; /** - * Unit tests for {@link PropertyComparator} - * - * @see org.springframework.util.comparator.ComparatorTests + * Unit tests for {@link PropertyComparator}. * * @author Keith Donald * @author Chris Beams @@ -40,7 +38,7 @@ public void testPropertyComparator() { Dog dog2 = new Dog(); dog2.setNickName("biscy"); - PropertyComparator c = new PropertyComparator("nickName", false, true); + PropertyComparator c = new PropertyComparator<>("nickName", false, true); assertTrue(c.compare(dog, dog2) > 0); assertTrue(c.compare(dog, dog) == 0); assertTrue(c.compare(dog2, dog) < 0); @@ -50,15 +48,14 @@ public void testPropertyComparator() { public void testPropertyComparatorNulls() { Dog dog = new Dog(); Dog dog2 = new Dog(); - PropertyComparator c = new PropertyComparator("nickName", false, true); + PropertyComparator c = new PropertyComparator<>("nickName", false, true); assertTrue(c.compare(dog, dog2) == 0); } - @SuppressWarnings("unchecked") @Test public void testCompoundComparator() { - CompoundComparator c = new CompoundComparator(); - c.addComparator(new PropertyComparator("lastName", false, true)); + CompoundComparator c = new CompoundComparator<>(); + c.addComparator(new PropertyComparator<>("lastName", false, true)); Dog dog1 = new Dog(); dog1.setFirstName("macy"); @@ -70,19 +67,19 @@ public void testCompoundComparator() { assertTrue(c.compare(dog1, dog2) == 0); - c.addComparator(new PropertyComparator("firstName", false, true)); + c.addComparator(new PropertyComparator<>("firstName", false, true)); assertTrue(c.compare(dog1, dog2) > 0); dog2.setLastName("konikk dog"); assertTrue(c.compare(dog2, dog1) > 0); } - @SuppressWarnings("unchecked") @Test public void testCompoundComparatorInvert() { - CompoundComparator c = new CompoundComparator(); - c.addComparator(new PropertyComparator("lastName", false, true)); - c.addComparator(new PropertyComparator("firstName", false, true)); + CompoundComparator c = new CompoundComparator<>(); + c.addComparator(new PropertyComparator<>("lastName", false, true)); + c.addComparator(new PropertyComparator<>("firstName", false, true)); + Dog dog1 = new Dog(); dog1.setFirstName("macy"); dog1.setLastName("grayspots"); @@ -97,7 +94,6 @@ public void testCompoundComparatorInvert() { } - @SuppressWarnings("unused") private static class Dog implements Comparable { private String nickName; @@ -106,11 +102,6 @@ private static class Dog implements Comparable { private String lastName; - @Override - public int compareTo(Object o) { - return nickName.compareTo(((Dog)o).nickName); - } - public String getNickName() { return nickName; } @@ -134,6 +125,11 @@ public String getLastName() { public void setLastName(String lastName) { this.lastName = lastName; } + + @Override + public int compareTo(Object o) { + return this.nickName.compareTo(((Dog) o).nickName); + } } } diff --git a/spring-beans/src/test/java/org/springframework/tests/sample/beans/TestBean.java b/spring-beans/src/test/java/org/springframework/tests/sample/beans/TestBean.java index da243abbc934..f1ee1d4df5db 100644 --- a/spring-beans/src/test/java/org/springframework/tests/sample/beans/TestBean.java +++ b/spring-beans/src/test/java/org/springframework/tests/sample/beans/TestBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -223,7 +223,7 @@ public void setSpouse(ITestBean spouse) { @Override public ITestBean[] getSpouses() { - return (spouse != null ? new ITestBean[]{spouse} : null); + return (spouse != null ? new ITestBean[] {spouse} : null); } public String getTouchy() { diff --git a/spring-beans/src/test/resources/org/springframework/beans/factory/xml/ProfileXmlBeanDefinitionTests-multiProfileNegated.xml b/spring-beans/src/test/resources/org/springframework/beans/factory/xml/ProfileXmlBeanDefinitionTests-multiProfileNegated.xml new file mode 100644 index 000000000000..a11d17043118 --- /dev/null +++ b/spring-beans/src/test/resources/org/springframework/beans/factory/xml/ProfileXmlBeanDefinitionTests-multiProfileNegated.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCache.java b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCache.java index f1b7fd927492..f5967110dcc6 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCache.java +++ b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,13 +41,15 @@ public class EhCacheCache implements Cache { /** * Create an {@link EhCacheCache} instance. - * @param ehcache backing Ehcache instance + * @param ehcache the backing Ehcache instance */ public EhCacheCache(Ehcache ehcache) { Assert.notNull(ehcache, "Ehcache must not be null"); Status status = ehcache.getStatus(); - Assert.isTrue(Status.STATUS_ALIVE.equals(status), - "An 'alive' Ehcache is required - current cache is " + status.toString()); + if (!Status.STATUS_ALIVE.equals(status)) { + throw new IllegalArgumentException( + "An 'alive' Ehcache is required - current cache is " + status.toString()); + } this.cache = ehcache; } @@ -78,7 +80,7 @@ public T get(Object key, Callable valueLoader) { else { this.cache.acquireWriteLockOnKey(key); try { - element = lookup(key); // One more attempt with the write lock + element = lookup(key); // one more attempt with the write lock if (element != null) { return (T) element.getObjectValue(); } @@ -98,7 +100,7 @@ private T loadValue(Object key, Callable valueLoader) { try { value = valueLoader.call(); } - catch (Exception ex) { + catch (Throwable ex) { throw new ValueRetrievalException(key, valueLoader, ex); } put(key, value); @@ -111,7 +113,8 @@ public T get(Object key, Class type) { Element element = this.cache.get(key); Object value = (element != null ? element.getObjectValue() : null); if (value != null && type != null && !type.isInstance(value)) { - throw new IllegalStateException("Cached value is not of required type [" + type.getName() + "]: " + value); + throw new IllegalStateException( + "Cached value is not of required type [" + type.getName() + "]: " + value); } return (T) value; } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCache.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCache.java index c80330846e58..fba487549a7a 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCache.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.cache.jcache; import java.util.concurrent.Callable; +import javax.cache.Cache; import javax.cache.processor.EntryProcessor; import javax.cache.processor.EntryProcessorException; import javax.cache.processor.MutableEntry; @@ -36,14 +37,14 @@ */ public class JCacheCache extends AbstractValueAdaptingCache { - private final javax.cache.Cache cache; + private final Cache cache; /** * Create an {@link org.springframework.cache.jcache.JCacheCache} instance. * @param jcache backing JCache Cache instance */ - public JCacheCache(javax.cache.Cache jcache) { + public JCacheCache(Cache jcache) { this(jcache, true); } @@ -52,7 +53,7 @@ public JCacheCache(javax.cache.Cache jcache) { * @param jcache backing JCache Cache instance * @param allowNullValues whether to accept and convert null values for this cache */ - public JCacheCache(javax.cache.Cache jcache, boolean allowNullValues) { + public JCacheCache(Cache jcache, boolean allowNullValues) { super(allowNullValues); Assert.notNull(jcache, "Cache must not be null"); this.cache = jcache; @@ -65,7 +66,7 @@ public final String getName() { } @Override - public final javax.cache.Cache getNativeCache() { + public final Cache getNativeCache() { return this.cache; } @@ -110,8 +111,7 @@ private class ValueLoaderEntryProcessor implements EntryProcessor entry, Object... arguments) - throws EntryProcessorException { + public T process(MutableEntry entry, Object... arguments) throws EntryProcessorException { Callable valueLoader = (Callable) arguments[0]; if (entry.exists()) { return (T) fromStoreValue(entry.getValue()); diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCacheManager.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCacheManager.java index 9c324efcddfd..96e2cd1ff322 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCacheManager.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCacheManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,7 @@ */ public class JCacheCacheManager extends AbstractTransactionSupportingCacheManager { - private javax.cache.CacheManager cacheManager; + private CacheManager cacheManager; private boolean allowNullValues = true; @@ -60,14 +60,14 @@ public JCacheCacheManager(CacheManager cacheManager) { /** * Set the backing JCache {@link javax.cache.CacheManager}. */ - public void setCacheManager(javax.cache.CacheManager cacheManager) { + public void setCacheManager(CacheManager cacheManager) { this.cacheManager = cacheManager; } /** * Return the backing JCache {@link javax.cache.CacheManager}. */ - public javax.cache.CacheManager getCacheManager() { + public CacheManager getCacheManager() { return this.cacheManager; } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/ProxyJCacheConfiguration.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/ProxyJCacheConfiguration.java index 028a6ccfb67d..cc23061096c2 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/ProxyJCacheConfiguration.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/ProxyJCacheConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ * @see org.springframework.cache.annotation.CachingConfigurationSelector */ @Configuration +@Role(BeanDefinition.ROLE_INFRASTRUCTURE) public class ProxyJCacheConfiguration extends AbstractJCacheConfiguration { @Bean(name = CacheManagementConfigUtils.JCACHE_ADVISOR_BEAN_NAME) diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractCacheInterceptor.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractCacheInterceptor.java index cac85264c367..800ceeec149e 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractCacheInterceptor.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractCacheInterceptor.java @@ -69,7 +69,7 @@ protected Cache resolveCache(CacheOperationInvocationContext context) { /** * Convert the collection of caches in a single expected element. *

Throw an {@link IllegalStateException} if the collection holds more than one element - * @return the singe element or {@code null} if the collection is empty + * @return the single element or {@code null} if the collection is empty */ static Cache extractFrom(Collection caches) { if (CollectionUtils.isEmpty(caches)) { diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheResolverAdapter.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheResolverAdapter.java index 369c6a128881..f1efb621efa1 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheResolverAdapter.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheResolverAdapter.java @@ -1,5 +1,20 @@ -package org.springframework.cache.jcache.interceptor; +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cache.jcache.interceptor; import java.util.Collection; import java.util.Collections; @@ -23,17 +38,19 @@ class CacheResolverAdapter implements CacheResolver { private final javax.cache.annotation.CacheResolver target; + /** * Create a new instance with the JSR-107 cache resolver to invoke. */ public CacheResolverAdapter(javax.cache.annotation.CacheResolver target) { - Assert.notNull(target, "JSR-107 cache resolver must be set."); + Assert.notNull(target, "JSR-107 CacheResolver is required"); this.target = target; } + /** - * Return the underlying {@link javax.cache.annotation.CacheResolver} that this - * instance is using. + * Return the underlying {@link javax.cache.annotation.CacheResolver} + * that this instance is using. */ protected javax.cache.annotation.CacheResolver getTarget() { return target; @@ -45,8 +62,10 @@ public Collection resolveCaches(CacheOperationInvocationContext throw new IllegalStateException("Unexpected context " + context); } CacheInvocationContext cacheInvocationContext = (CacheInvocationContext) context; - javax.cache.Cache cache = target.resolveCache(cacheInvocationContext); - Assert.notNull(cache, "Cannot resolve cache for '" + context + "' using '" + target + "'"); + javax.cache.Cache cache = this.target.resolveCache(cacheInvocationContext); + if (cache == null) { + throw new IllegalStateException("Could not resolve cache for " + context + " using " + this.target); + } return Collections.singleton(new JCacheCache(cache)); } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheResultInterceptor.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheResultInterceptor.java index 7c178edf02d6..3f01b009c042 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheResultInterceptor.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheResultInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,7 @@ public CacheResultInterceptor(CacheErrorHandler errorHandler) { super(errorHandler); } + @Override protected Object invoke(CacheOperationInvocationContext context, CacheOperationInvoker invoker) { @@ -59,7 +60,7 @@ protected Object invoke(CacheOperationInvocationContext co try { Object invocationResult = invoker.invoke(); - cache.put(cacheKey, invocationResult); + doPut(cache, cacheKey, invocationResult); return invocationResult; } catch (CacheOperationInvoker.ThrowableWrapper ex) { @@ -82,17 +83,15 @@ protected void checkForCachedException(Cache exceptionCache, Object cacheKey) { } } - protected void cacheException(Cache exceptionCache, ExceptionTypeFilter filter, Object cacheKey, Throwable ex) { if (exceptionCache == null) { return; } if (filter.match(ex.getClass())) { - exceptionCache.put(cacheKey, ex); + doPut(exceptionCache, cacheKey, ex); } } - private Cache resolveExceptionCache(CacheOperationInvocationContext context) { CacheResolver exceptionCacheResolver = context.getOperation().getExceptionCacheResolver(); if (exceptionCacheResolver != null) { @@ -101,9 +100,10 @@ private Cache resolveExceptionCache(CacheOperationInvocationContextClone the specified exception. If the exception is not {@code serializable}, * the original exception is returned. If no common ancestor can be found, returns * the original exception. @@ -111,8 +111,8 @@ private Cache resolveExceptionCache(CacheOperationInvocationContext private final CacheInvocationParameter[] allParameters; - public DefaultCacheInvocationContext(JCacheOperation operation, - Object target, Object[] args) { + + public DefaultCacheInvocationContext(JCacheOperation operation, Object target, Object[] args) { this.operation = operation; this.target = target; this.args = args; this.allParameters = operation.getAllParameters(args); } + @Override public JCacheOperation getOperation() { return this.operation; @@ -94,17 +95,19 @@ public CacheInvocationParameter[] getAllParameters() { @Override public T unwrap(Class cls) { - throw new IllegalArgumentException("Could not unwrap to '" + cls.getName() + "'"); + throw new IllegalArgumentException("Cannot unwrap to " + cls); } + @Override public String toString() { - final StringBuilder sb = new StringBuilder("CacheInvocationContext{"); - sb.append("operation=").append(operation); - sb.append(", target=").append(target); - sb.append(", args=").append(Arrays.toString(args)); - sb.append(", allParameters=").append(Arrays.toString(allParameters)); + StringBuilder sb = new StringBuilder("CacheInvocationContext{"); + sb.append("operation=").append(this.operation); + sb.append(", target=").append(this.target); + sb.append(", args=").append(Arrays.toString(this.args)); + sb.append(", allParameters=").append(Arrays.toString(this.allParameters)); sb.append('}'); return sb.toString(); } + } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheAspectSupport.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheAspectSupport.java index b20f7bbe25ec..ee25a1090ab1 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheAspectSupport.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheAspectSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -66,7 +66,7 @@ public class JCacheAspectSupport extends AbstractCacheInvoker implements Initial public void setCacheOperationSource(JCacheOperationSource cacheOperationSource) { - Assert.notNull(cacheOperationSource); + Assert.notNull(cacheOperationSource, "JCacheOperationSource must not be null"); this.cacheOperationSource = cacheOperationSource; } @@ -80,7 +80,7 @@ public JCacheOperationSource getCacheOperationSource() { public void afterPropertiesSet() { Assert.state(getCacheOperationSource() != null, "The 'cacheOperationSource' property is required: " + "If there are no cacheable methods, then don't use a cache aspect."); - Assert.state(getErrorHandler() != null, "The 'errorHandler' is required"); + Assert.state(getErrorHandler() != null, "The 'errorHandler' property is required"); this.cacheResultInterceptor = new CacheResultInterceptor(getErrorHandler()); this.cachePutInterceptor = new CachePutInterceptor(getErrorHandler()); @@ -94,7 +94,7 @@ public void afterPropertiesSet() { protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) { // Check whether aspect is enabled to cope with cases where the AJ is pulled in automatically if (this.initialized) { - Class targetClass = getTargetClass(target); + Class targetClass = AopProxyUtils.ultimateTargetClass(target); JCacheOperation operation = getCacheOperationSource().getCacheOperation(method, targetClass); if (operation != null) { CacheOperationInvocationContext context = @@ -114,37 +114,29 @@ private CacheOperationInvocationContext createCacheOperationInvocationContext (JCacheOperation) operation, target, args); } - private Class getTargetClass(Object target) { - Class targetClass = AopProxyUtils.ultimateTargetClass(target); - if (targetClass == null && target != null) { - targetClass = target.getClass(); - } - return targetClass; - } - @SuppressWarnings("unchecked") private Object execute(CacheOperationInvocationContext context, CacheOperationInvoker invoker) { CacheOperationInvoker adapter = new CacheOperationInvokerAdapter(invoker); BasicOperation operation = context.getOperation(); if (operation instanceof CacheResultOperation) { - return cacheResultInterceptor.invoke( + return this.cacheResultInterceptor.invoke( (CacheOperationInvocationContext) context, adapter); } else if (operation instanceof CachePutOperation) { - return cachePutInterceptor.invoke( + return this.cachePutInterceptor.invoke( (CacheOperationInvocationContext) context, adapter); } else if (operation instanceof CacheRemoveOperation) { - return cacheRemoveEntryInterceptor.invoke( + return this.cacheRemoveEntryInterceptor.invoke( (CacheOperationInvocationContext) context, adapter); } else if (operation instanceof CacheRemoveAllOperation) { - return cacheRemoveAllInterceptor.invoke( + return this.cacheRemoveAllInterceptor.invoke( (CacheOperationInvocationContext) context, adapter); } else { - throw new IllegalArgumentException("Could not handle " + operation); + throw new IllegalArgumentException("Cannot handle " + operation); } } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/KeyGeneratorAdapter.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/KeyGeneratorAdapter.java index 42db86cefdb6..4c6bccb2c18d 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/KeyGeneratorAdapter.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/KeyGeneratorAdapter.java @@ -1,3 +1,19 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.cache.jcache.interceptor; import java.lang.annotation.Annotation; @@ -13,10 +29,9 @@ import org.springframework.util.CollectionUtils; /** - * Spring's {@link KeyGenerator} implementation that either delegates to a - * standard JSR-107 {@link javax.cache.annotation.CacheKeyGenerator}, or - * wrap a standard {@link KeyGenerator} so that only relevant parameters - * are handled. + * Spring's {@link KeyGenerator} implementation that either delegates to a standard JSR-107 + * {@link javax.cache.annotation.CacheKeyGenerator}, or wrap a standard {@link KeyGenerator} + * so that only relevant parameters are handled. * * @author Stephane Nicoll * @since 4.1 @@ -29,12 +44,13 @@ class KeyGeneratorAdapter implements KeyGenerator { private CacheKeyGenerator cacheKeyGenerator; + /** * Create an instance with the given {@link KeyGenerator} so that {@link javax.cache.annotation.CacheKey} * and {@link javax.cache.annotation.CacheValue} are handled according to the spec. */ public KeyGeneratorAdapter(JCacheOperationSource cacheOperationSource, KeyGenerator target) { - Assert.notNull(cacheOperationSource, "cacheOperationSource must not be null."); + Assert.notNull(cacheOperationSource, "JCacheOperationSource must not be null"); Assert.notNull(target, "KeyGenerator must not be null"); this.cacheOperationSource = cacheOperationSource; this.keyGenerator = target; @@ -44,12 +60,13 @@ public KeyGeneratorAdapter(JCacheOperationSource cacheOperationSource, KeyGenera * Create an instance used to wrap the specified {@link javax.cache.annotation.CacheKeyGenerator}. */ public KeyGeneratorAdapter(JCacheOperationSource cacheOperationSource, CacheKeyGenerator target) { - Assert.notNull(cacheOperationSource, "cacheOperationSource must not be null."); - Assert.notNull(target, "KeyGenerator must not be null"); + Assert.notNull(cacheOperationSource, "JCacheOperationSource must not be null"); + Assert.notNull(target, "CacheKeyGenerator must not be null"); this.cacheOperationSource = cacheOperationSource; this.cacheKeyGenerator = target; } + /** * Return the target key generator to use in the form of either a {@link KeyGenerator} * or a {@link CacheKeyGenerator}. @@ -58,7 +75,6 @@ public Object getTarget() { return (this.keyGenerator != null ? this.keyGenerator : this.cacheKeyGenerator); } - @Override public Object generate(Object target, Method method, Object... params) { JCacheOperation operation = this.cacheOperationSource.getCacheOperation(method, target.getClass()); @@ -88,15 +104,14 @@ private static Object doGenerate(KeyGenerator keyGenerator, CacheKeyInvocationCo parameters.add(value); } } - return keyGenerator.generate(context.getTarget(), context.getMethod(), - parameters.toArray(new Object[parameters.size()])); - + return keyGenerator.generate(context.getTarget(), context.getMethod(), parameters.toArray()); } @SuppressWarnings("unchecked") - private CacheKeyInvocationContext createCacheKeyInvocationContext(Object target, - JCacheOperation operation, Object[] params) { + private CacheKeyInvocationContext createCacheKeyInvocationContext( + Object target, JCacheOperation operation, Object[] params) { + AbstractJCacheKeyOperation keyCacheOperation = (AbstractJCacheKeyOperation) operation; return new DefaultCacheKeyInvocationContext(keyCacheOperation, target, params); } diff --git a/spring-context-support/src/main/java/org/springframework/cache/transaction/TransactionAwareCacheDecorator.java b/spring-context-support/src/main/java/org/springframework/cache/transaction/TransactionAwareCacheDecorator.java index f96c04c07188..a7c843ddb67c 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/transaction/TransactionAwareCacheDecorator.java +++ b/spring-context-support/src/main/java/org/springframework/cache/transaction/TransactionAwareCacheDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,6 +53,12 @@ public TransactionAwareCacheDecorator(Cache targetCache) { this.targetCache = targetCache; } + /** + * Return the target Cache that this Cache should delegate to. + */ + public Cache getTargetCache() { + return this.targetCache; + } @Override public String getName() { @@ -85,7 +91,7 @@ public void put(final Object key, final Object value) { TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCommit() { - targetCache.put(key, value); + TransactionAwareCacheDecorator.this.targetCache.put(key, value); } }); } @@ -105,7 +111,7 @@ public void evict(final Object key) { TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCommit() { - targetCache.evict(key); + TransactionAwareCacheDecorator.this.targetCache.evict(key); } }); } diff --git a/spring-context-support/src/main/java/org/springframework/mail/MailMessage.java b/spring-context-support/src/main/java/org/springframework/mail/MailMessage.java index c33f0e76b24d..955903621117 100644 --- a/spring-context-support/src/main/java/org/springframework/mail/MailMessage.java +++ b/spring-context-support/src/main/java/org/springframework/mail/MailMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2005 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,26 +35,26 @@ */ public interface MailMessage { - public void setFrom(String from) throws MailParseException; + void setFrom(String from) throws MailParseException; - public void setReplyTo(String replyTo) throws MailParseException; + void setReplyTo(String replyTo) throws MailParseException; - public void setTo(String to) throws MailParseException; + void setTo(String to) throws MailParseException; - public void setTo(String[] to) throws MailParseException; + void setTo(String[] to) throws MailParseException; - public void setCc(String cc) throws MailParseException; + void setCc(String cc) throws MailParseException; - public void setCc(String[] cc) throws MailParseException; + void setCc(String[] cc) throws MailParseException; - public void setBcc(String bcc) throws MailParseException; + void setBcc(String bcc) throws MailParseException; - public void setBcc(String[] bcc) throws MailParseException; + void setBcc(String[] bcc) throws MailParseException; - public void setSentDate(Date sentDate) throws MailParseException; + void setSentDate(Date sentDate) throws MailParseException; - public void setSubject(String subject) throws MailParseException; + void setSubject(String subject) throws MailParseException; - public void setText(String text) throws MailParseException; + void setText(String text) throws MailParseException; } diff --git a/spring-context-support/src/main/java/org/springframework/mail/SimpleMailMessage.java b/spring-context-support/src/main/java/org/springframework/mail/SimpleMailMessage.java index bca30499d954..862fc6881b38 100644 --- a/spring-context-support/src/main/java/org/springframework/mail/SimpleMailMessage.java +++ b/spring-context-support/src/main/java/org/springframework/mail/SimpleMailMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,8 @@ import org.springframework.util.StringUtils; /** - * Models a simple mail message, including data such as the from, to, cc, subject, and text fields. + * Models a simple mail message, including data such as the from, to, cc, subject, + * and text fields. * *

Consider {@code JavaMailSender} and JavaMail {@code MimeMessages} for creating * more sophisticated messages, for example messages with attachments, special @@ -68,21 +69,14 @@ public SimpleMailMessage() { /** * Copy constructor for creating a new {@code SimpleMailMessage} from the state * of an existing {@code SimpleMailMessage} instance. - * @throws IllegalArgumentException if the supplied message is {@code null} */ public SimpleMailMessage(SimpleMailMessage original) { - Assert.notNull(original, "The 'original' message argument cannot be null"); + Assert.notNull(original, "'original' message argument must not be null"); this.from = original.getFrom(); this.replyTo = original.getReplyTo(); - if (original.getTo() != null) { - this.to = copy(original.getTo()); - } - if (original.getCc() != null) { - this.cc = copy(original.getCc()); - } - if (original.getBcc() != null) { - this.bcc = copy(original.getBcc()); - } + this.to = copyOrNull(original.getTo()); + this.cc = copyOrNull(original.getCc()); + this.bcc = copyOrNull(original.getBcc()); this.sentDate = original.getSentDate(); this.subject = original.getSubject(); this.text = original.getText(); @@ -104,7 +98,7 @@ public void setReplyTo(String replyTo) { } public String getReplyTo() { - return replyTo; + return this.replyTo; } @Override @@ -132,7 +126,7 @@ public void setCc(String[] cc) { } public String[] getCc() { - return cc; + return this.cc; } @Override @@ -146,7 +140,7 @@ public void setBcc(String[] bcc) { } public String[] getBcc() { - return bcc; + return this.bcc; } @Override @@ -155,7 +149,7 @@ public void setSentDate(Date sentDate) { } public Date getSentDate() { - return sentDate; + return this.sentDate; } @Override @@ -180,10 +174,9 @@ public String getText() { /** * Copy the contents of this message to the given target message. * @param target the {@code MailMessage} to copy to - * @throws IllegalArgumentException if the supplied {@code target} is {@code null} */ public void copyTo(MailMessage target) { - Assert.notNull(target, "The 'target' message argument cannot be null"); + Assert.notNull(target, "'target' MailMessage must not be null"); if (getFrom() != null) { target.setFrom(getFrom()); } @@ -191,13 +184,13 @@ public void copyTo(MailMessage target) { target.setReplyTo(getReplyTo()); } if (getTo() != null) { - target.setTo(getTo()); + target.setTo(copy(getTo())); } if (getCc() != null) { - target.setCc(getCc()); + target.setCc(copy(getCc())); } if (getBcc() != null) { - target.setBcc(getBcc()); + target.setBcc(copy(getBcc())); } if (getSentDate() != null) { target.setSentDate(getSentDate()); @@ -211,20 +204,6 @@ public void copyTo(MailMessage target) { } - @Override - public String toString() { - StringBuilder sb = new StringBuilder("SimpleMailMessage: "); - sb.append("from=").append(this.from).append("; "); - sb.append("replyTo=").append(this.replyTo).append("; "); - sb.append("to=").append(StringUtils.arrayToCommaDelimitedString(this.to)).append("; "); - sb.append("cc=").append(StringUtils.arrayToCommaDelimitedString(this.cc)).append("; "); - sb.append("bcc=").append(StringUtils.arrayToCommaDelimitedString(this.bcc)).append("; "); - sb.append("sentDate=").append(this.sentDate).append("; "); - sb.append("subject=").append(this.subject).append("; "); - sb.append("text=").append(this.text); - return sb.toString(); - } - @Override public boolean equals(Object other) { if (this == other) { @@ -236,9 +215,9 @@ public boolean equals(Object other) { SimpleMailMessage otherMessage = (SimpleMailMessage) other; return (ObjectUtils.nullSafeEquals(this.from, otherMessage.from) && ObjectUtils.nullSafeEquals(this.replyTo, otherMessage.replyTo) && - java.util.Arrays.equals(this.to, otherMessage.to) && - java.util.Arrays.equals(this.cc, otherMessage.cc) && - java.util.Arrays.equals(this.bcc, otherMessage.bcc) && + ObjectUtils.nullSafeEquals(this.to, otherMessage.to) && + ObjectUtils.nullSafeEquals(this.cc, otherMessage.cc) && + ObjectUtils.nullSafeEquals(this.bcc, otherMessage.bcc) && ObjectUtils.nullSafeEquals(this.sentDate, otherMessage.sentDate) && ObjectUtils.nullSafeEquals(this.subject, otherMessage.subject) && ObjectUtils.nullSafeEquals(this.text, otherMessage.text)); @@ -246,23 +225,37 @@ public boolean equals(Object other) { @Override public int hashCode() { - int hashCode = (this.from == null ? 0 : this.from.hashCode()); - hashCode = 29 * hashCode + (this.replyTo == null ? 0 : this.replyTo.hashCode()); - for (int i = 0; this.to != null && i < this.to.length; i++) { - hashCode = 29 * hashCode + (this.to == null ? 0 : this.to[i].hashCode()); - } - for (int i = 0; this.cc != null && i < this.cc.length; i++) { - hashCode = 29 * hashCode + (this.cc == null ? 0 : this.cc[i].hashCode()); - } - for (int i = 0; this.bcc != null && i < this.bcc.length; i++) { - hashCode = 29 * hashCode + (this.bcc == null ? 0 : this.bcc[i].hashCode()); - } - hashCode = 29 * hashCode + (this.sentDate == null ? 0 : this.sentDate.hashCode()); - hashCode = 29 * hashCode + (this.subject == null ? 0 : this.subject.hashCode()); - hashCode = 29 * hashCode + (this.text == null ? 0 : this.text.hashCode()); + int hashCode = ObjectUtils.nullSafeHashCode(this.from); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.replyTo); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.to); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.cc); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.bcc); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.sentDate); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.subject); return hashCode; } + @Override + public String toString() { + StringBuilder sb = new StringBuilder("SimpleMailMessage: "); + sb.append("from=").append(this.from).append("; "); + sb.append("replyTo=").append(this.replyTo).append("; "); + sb.append("to=").append(StringUtils.arrayToCommaDelimitedString(this.to)).append("; "); + sb.append("cc=").append(StringUtils.arrayToCommaDelimitedString(this.cc)).append("; "); + sb.append("bcc=").append(StringUtils.arrayToCommaDelimitedString(this.bcc)).append("; "); + sb.append("sentDate=").append(this.sentDate).append("; "); + sb.append("subject=").append(this.subject).append("; "); + sb.append("text=").append(this.text); + return sb.toString(); + } + + + private static String[] copyOrNull(String[] state) { + if (state == null) { + return null; + } + return copy(state); + } private static String[] copy(String[] state) { String[] copy = new String[state.length]; diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/commonj/ScheduledTimerListener.java b/spring-context-support/src/main/java/org/springframework/scheduling/commonj/ScheduledTimerListener.java index c3e603882dc7..9f573cfe16f5 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/commonj/ScheduledTimerListener.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/commonj/ScheduledTimerListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -173,7 +173,7 @@ public long getDelay() { * Set the period between repeated task executions, in milliseconds. *

Default is -1, leading to one-time execution. In case of zero or a * positive value, the task will be executed repeatedly, with the given - * interval inbetween executions. + * interval in-between executions. *

Note that the semantics of the period value vary between fixed-rate * and fixed-delay execution. *

Note: A period of 0 (for example as fixed delay) is diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessor.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessor.java index f29ca0697216..f48a03d607e8 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessor.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -131,7 +131,7 @@ public void setJobDetails(JobDetail... jobDetails) { /** * Register a list of Quartz Calendar objects with the Scheduler * that this FactoryBean creates, to be referenced by Triggers. - * @param calendars Map with calendar names as keys as Calendar + * @param calendars a Map with calendar names as keys as Calendar * objects as values * @see org.quartz.Calendar */ @@ -299,7 +299,15 @@ private boolean addTriggerToScheduler(Trigger trigger) throws SchedulerException if (jobDetail != null && !this.jobDetails.contains(jobDetail) && addJobToScheduler(jobDetail)) { this.jobDetails.add(jobDetail); } - getScheduler().rescheduleJob(trigger.getKey(), trigger); + try { + getScheduler().rescheduleJob(trigger.getKey(), trigger); + } + catch (ObjectAlreadyExistsException ex) { + if (logger.isDebugEnabled()) { + logger.debug("Unexpectedly encountered existing trigger on rescheduling, assumably due to " + + "cluster race condition: " + ex.getMessage() + " - can safely be ignored"); + } + } } else { try { @@ -314,8 +322,8 @@ private boolean addTriggerToScheduler(Trigger trigger) throws SchedulerException } catch (ObjectAlreadyExistsException ex) { if (logger.isDebugEnabled()) { - logger.debug("Unexpectedly found existing trigger, assumably due to cluster race condition: " + - ex.getMessage() + " - can safely be ignored"); + logger.debug("Unexpectedly encountered existing trigger on job scheduling, assumably due to " + + "cluster race condition: " + ex.getMessage() + " - can safely be ignored"); } if (this.overwriteExistingJobs) { getScheduler().rescheduleJob(trigger.getKey(), trigger); 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 c3f85ae80c54..9d273091721b 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-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,6 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.scheduling.SchedulingException; -import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; /** @@ -106,11 +105,10 @@ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBe new ThreadLocal(); /** - * Return the ResourceLoader for the currently configured Quartz Scheduler, - * to be used by ResourceLoaderClassLoadHelper. - *

This instance will be set before initialization of the corresponding - * Scheduler, and reset immediately afterwards. It is thus only available - * during configuration. + * Return the {@link ResourceLoader} for the currently configured Quartz Scheduler, + * to be used by {@link ResourceLoaderClassLoadHelper}. + *

This instance will be set before initialization of the corresponding Scheduler, + * and reset immediately afterwards. It is thus only available during configuration. * @see #setApplicationContext * @see ResourceLoaderClassLoadHelper */ @@ -119,11 +117,11 @@ public static ResourceLoader getConfigTimeResourceLoader() { } /** - * Return the TaskExecutor for the currently configured Quartz Scheduler, - * to be used by LocalTaskExecutorThreadPool. - *

This instance will be set before initialization of the corresponding - * Scheduler, and reset immediately afterwards. It is thus only available - * during configuration. + * Return the {@link Executor} for the currently configured Quartz Scheduler, + * to be used by {@link LocalTaskExecutorThreadPool}. + *

This instance will be set before initialization of the corresponding Scheduler, + * and reset immediately afterwards. It is thus only available during configuration. + * @since 2.0 * @see #setTaskExecutor * @see LocalTaskExecutorThreadPool */ @@ -132,11 +130,11 @@ public static Executor getConfigTimeTaskExecutor() { } /** - * Return the DataSource for the currently configured Quartz Scheduler, - * to be used by LocalDataSourceJobStore. - *

This instance will be set before initialization of the corresponding - * Scheduler, and reset immediately afterwards. It is thus only available - * during configuration. + * Return the {@link DataSource} for the currently configured Quartz Scheduler, + * to be used by {@link LocalDataSourceJobStore}. + *

This instance will be set before initialization of the corresponding Scheduler, + * and reset immediately afterwards. It is thus only available during configuration. + * @since 1.1 * @see #setDataSource * @see LocalDataSourceJobStore */ @@ -145,11 +143,11 @@ public static DataSource getConfigTimeDataSource() { } /** - * Return the non-transactional DataSource for the currently configured - * Quartz Scheduler, to be used by LocalDataSourceJobStore. - *

This instance will be set before initialization of the corresponding - * Scheduler, and reset immediately afterwards. It is thus only available - * during configuration. + * Return the non-transactional {@link DataSource} for the currently configured + * Quartz Scheduler, to be used by {@link LocalDataSourceJobStore}. + *

This instance will be set before initialization of the corresponding Scheduler, + * and reset immediately afterwards. It is thus only available during configuration. + * @since 1.1 * @see #setNonTransactionalDataSource * @see LocalDataSourceJobStore */ @@ -158,6 +156,8 @@ public static DataSource getConfigTimeNonTransactionalDataSource() { } + private SchedulerFactory schedulerFactory; + private Class schedulerFactoryClass = StdSchedulerFactory.class; private String schedulerName; @@ -166,14 +166,12 @@ public static DataSource getConfigTimeNonTransactionalDataSource() { private Properties quartzProperties; - private Executor taskExecutor; private DataSource dataSource; private DataSource nonTransactionalDataSource; - private Map schedulerContextMap; private ApplicationContext applicationContext; @@ -184,7 +182,6 @@ public static DataSource getConfigTimeNonTransactionalDataSource() { private boolean jobFactorySet = false; - private boolean autoStartup = true; private int startupDelay = 0; @@ -195,22 +192,40 @@ public static DataSource getConfigTimeNonTransactionalDataSource() { private boolean waitForJobsToCompleteOnShutdown = false; - private Scheduler scheduler; /** - * Set the Quartz SchedulerFactory implementation to use. - *

Default is {@link StdSchedulerFactory}, reading in the standard - * {@code quartz.properties} from {@code quartz.jar}. - * To use custom Quartz properties, specify the "configLocation" - * or "quartzProperties" bean property on this FactoryBean. + * Set an external Quartz {@link SchedulerFactory} instance to use. + *

Default is an internal {@link StdSchedulerFactory} instance. If this method is + * called, it overrides any class specified through {@link #setSchedulerFactoryClass} + * as well as any settings specified through {@link #setConfigLocation}, + * {@link #setQuartzProperties}, {@link #setTaskExecutor} or {@link #setDataSource}. + *

NOTE: With an externally provided {@code SchedulerFactory} instance, + * local settings such as {@link #setConfigLocation} or {@link #setQuartzProperties} + * will be ignored here in {@code SchedulerFactoryBean}, expecting the external + * {@code SchedulerFactory} instance to get initialized on its own. + * @since 4.3.15 + * @see #setSchedulerFactoryClass + */ + public void setSchedulerFactory(SchedulerFactory schedulerFactory) { + this.schedulerFactory = schedulerFactory; + } + + /** + * Set the Quartz {@link SchedulerFactory} implementation to use. + *

Default is the {@link StdSchedulerFactory} class, reading in the standard + * {@code quartz.properties} from {@code quartz.jar}. For applying custom Quartz + * properties, specify {@link #setConfigLocation "configLocation"} and/or + * {@link #setQuartzProperties "quartzProperties"} etc on this local + * {@code SchedulerFactoryBean} instance. * @see org.quartz.impl.StdSchedulerFactory * @see #setConfigLocation * @see #setQuartzProperties + * @see #setTaskExecutor + * @see #setDataSource */ public void setSchedulerFactoryClass(Class schedulerFactoryClass) { - Assert.isAssignable(SchedulerFactory.class, schedulerFactoryClass); this.schedulerFactoryClass = schedulerFactoryClass; } @@ -246,26 +261,26 @@ public void setQuartzProperties(Properties quartzProperties) { this.quartzProperties = quartzProperties; } - /** - * Set the Spring TaskExecutor to use as Quartz backend. + * Set a Spring-managed {@link Executor} to use as Quartz backend. * Exposed as thread pool through the Quartz SPI. - *

Can be used to assign a JDK 1.5 ThreadPoolExecutor or a CommonJ + *

Can be used to assign a local JDK ThreadPoolExecutor or a CommonJ * WorkManager as Quartz backend, to avoid Quartz's manual thread creation. *

By default, a Quartz SimpleThreadPool will be used, configured through * the corresponding Quartz properties. + * @since 2.0 * @see #setQuartzProperties * @see LocalTaskExecutorThreadPool * @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor - * @see org.springframework.scheduling.commonj.WorkManagerTaskExecutor + * @see org.springframework.scheduling.concurrent.DefaultManagedTaskExecutor */ public void setTaskExecutor(Executor taskExecutor) { this.taskExecutor = taskExecutor; } /** - * Set the default DataSource to be used by the Scheduler. If set, - * this will override corresponding settings in Quartz properties. + * 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. *

A Spring-specific subclass of Quartz' JobStoreCMT will be used. @@ -278,6 +293,7 @@ public void setTaskExecutor(Executor taskExecutor) { * argument is sufficient. In case of an XA DataSource and global JTA transactions, * SchedulerFactoryBean's "nonTransactionalDataSource" property should be set, * passing in a non-XA DataSource that will not participate in global transactions. + * @since 1.1 * @see #setNonTransactionalDataSource * @see #setQuartzProperties * @see #setTransactionManager @@ -288,12 +304,13 @@ public void setDataSource(DataSource dataSource) { } /** - * Set the DataSource to be used by the Scheduler for non-transactional access. + * Set the {@link DataSource} to be used for non-transactional access. *

This is only necessary if the default DataSource is an XA DataSource that will * always participate in transactions: A non-XA version of that DataSource should * be specified as "nonTransactionalDataSource" in such a scenario. *

This is not relevant with a local DataSource instance and Spring transactions. * Specifying a single default DataSource as "dataSource" is sufficient there. + * @since 1.1 * @see #setDataSource * @see LocalDataSourceJobStore */ @@ -301,14 +318,13 @@ public void setNonTransactionalDataSource(DataSource nonTransactionalDataSource) this.nonTransactionalDataSource = nonTransactionalDataSource; } - /** * Register objects in the Scheduler context via a given Map. * These objects will be available to any Job that runs in this Scheduler. *

Note: When using persistent Jobs whose JobDetail will be kept in the * database, do not put Spring-managed beans or an ApplicationContext * reference into the JobDataMap but rather into the SchedulerContext. - * @param schedulerContextAsMap Map with String keys and any objects as + * @param schedulerContextAsMap a Map with String keys and any objects as * values (for example Spring-managed beans) * @see JobDetailFactoryBean#setJobDataAsMap */ @@ -317,7 +333,7 @@ public void setSchedulerContextAsMap(Map schedulerContextAsMap) { } /** - * Set the key of an ApplicationContext reference to expose in the + * Set the key of an {@link ApplicationContext} reference to expose in the * SchedulerContext, for example "applicationContext". Default is none. * Only applicable when running in a Spring ApplicationContext. *

Note: When using persistent Jobs whose JobDetail will be kept in the @@ -337,7 +353,7 @@ public void setApplicationContextSchedulerContextKey(String applicationContextSc } /** - * Set the Quartz JobFactory to use for this Scheduler. + * Set the Quartz {@link JobFactory} to use for this Scheduler. *

Default is Spring's {@link AdaptableJobFactory}, which supports * {@link java.lang.Runnable} objects as well as standard Quartz * {@link org.quartz.Job} instances. Note that this default only applies @@ -346,6 +362,7 @@ public void setApplicationContextSchedulerContextKey(String applicationContextSc *

Specify an instance of Spring's {@link SpringBeanJobFactory} here * (typically as an inner bean definition) to automatically populate a job's * bean properties from the specified job data map and scheduler context. + * @since 2.0 * @see AdaptableJobFactory * @see SpringBeanJobFactory */ @@ -354,7 +371,6 @@ public void setJobFactory(JobFactory jobFactory) { this.jobFactorySet = true; } - /** * Set whether to automatically start the scheduler after initialization. *

Default is "true"; set this to "false" to allow for manual startup. @@ -374,11 +390,12 @@ public boolean isAutoStartup() { } /** - * Specify the phase in which this scheduler should be started and - * stopped. The startup order proceeds from lowest to highest, and - * the shutdown order is the reverse of that. By default this value - * is Integer.MAX_VALUE meaning that this scheduler starts as late - * as possible and stops as soon as possible. + * Specify the phase in which this scheduler should be started and stopped. + * The startup order proceeds from lowest to highest, and the shutdown order + * is the reverse of that. By default this value is {@code Integer.MAX_VALUE} + * meaning that this scheduler starts as late as possible and stops as soon + * as possible. + * @since 3.0 */ public void setPhase(int phase) { this.phase = phase; @@ -426,7 +443,6 @@ public void setWaitForJobsToCompleteOnShutdown(boolean waitForJobsToCompleteOnSh this.waitForJobsToCompleteOnShutdown = waitForJobsToCompleteOnShutdown; } - @Override public void setBeanName(String name) { if (this.schedulerName == null) { @@ -454,82 +470,53 @@ public void afterPropertiesSet() throws Exception { this.resourceLoader = this.applicationContext; } - // Create SchedulerFactory instance... - SchedulerFactory schedulerFactory = BeanUtils.instantiateClass(this.schedulerFactoryClass); - initSchedulerFactory(schedulerFactory); - - if (this.resourceLoader != null) { - // Make given ResourceLoader available for SchedulerFactory configuration. - configTimeResourceLoaderHolder.set(this.resourceLoader); - } - if (this.taskExecutor != null) { - // Make given TaskExecutor available for SchedulerFactory configuration. - configTimeTaskExecutorHolder.set(this.taskExecutor); - } - if (this.dataSource != null) { - // Make given DataSource available for SchedulerFactory configuration. - configTimeDataSourceHolder.set(this.dataSource); - } - if (this.nonTransactionalDataSource != null) { - // Make given non-transactional DataSource available for SchedulerFactory configuration. - configTimeNonTransactionalDataSourceHolder.set(this.nonTransactionalDataSource); - } - - // Get Scheduler instance from SchedulerFactory. + // Initialize the Scheduler instance... + this.scheduler = prepareScheduler(prepareSchedulerFactory()); try { - this.scheduler = createScheduler(schedulerFactory, this.schedulerName); - populateSchedulerContext(); - - if (!this.jobFactorySet && !(this.scheduler instanceof RemoteScheduler)) { - // Use AdaptableJobFactory as default for a local Scheduler, unless when - // explicitly given a null value through the "jobFactory" bean property. - this.jobFactory = new AdaptableJobFactory(); - } - if (this.jobFactory != null) { - if (this.jobFactory instanceof SchedulerContextAware) { - ((SchedulerContextAware) this.jobFactory).setSchedulerContext(this.scheduler.getContext()); - } - this.scheduler.setJobFactory(this.jobFactory); - } + registerListeners(); + registerJobsAndTriggers(); } - - finally { - if (this.resourceLoader != null) { - configTimeResourceLoaderHolder.remove(); - } - if (this.taskExecutor != null) { - configTimeTaskExecutorHolder.remove(); + catch (Exception ex) { + try { + this.scheduler.shutdown(true); } - if (this.dataSource != null) { - configTimeDataSourceHolder.remove(); - } - if (this.nonTransactionalDataSource != null) { - configTimeNonTransactionalDataSourceHolder.remove(); + catch (Exception ex2) { + logger.debug("Scheduler shutdown exception after registration failure", ex2); } + throw ex; } - - registerListeners(); - registerJobsAndTriggers(); } /** - * Load and/or apply Quartz properties to the given SchedulerFactory. - * @param schedulerFactory the SchedulerFactory to initialize + * Create a SchedulerFactory if necessary and apply locally defined Quartz properties to it. + * @return the initialized SchedulerFactory */ - private void initSchedulerFactory(SchedulerFactory schedulerFactory) throws SchedulerException, IOException { - if (!(schedulerFactory instanceof StdSchedulerFactory)) { - if (this.configLocation != null || this.quartzProperties != null || + private SchedulerFactory prepareSchedulerFactory() throws SchedulerException, IOException { + SchedulerFactory schedulerFactory = this.schedulerFactory; + if (schedulerFactory == null) { + // Create local SchedulerFactory instance (typically a StdSchedulerFactory) + schedulerFactory = BeanUtils.instantiateClass(this.schedulerFactoryClass); + if (schedulerFactory instanceof StdSchedulerFactory) { + initSchedulerFactory((StdSchedulerFactory) schedulerFactory); + } + else if (this.configLocation != null || this.quartzProperties != null || this.taskExecutor != null || this.dataSource != null) { throw new IllegalArgumentException( "StdSchedulerFactory required for applying Quartz properties: " + schedulerFactory); } - // Otherwise assume that no initialization is necessary... - return; + // Otherwise, no local settings to be applied via StdSchedulerFactory.initialize(Properties) } + // Otherwise, assume that externally provided factory has been initialized with appropriate settings + return schedulerFactory; + } + /** + * Initialize the given SchedulerFactory, applying locally defined Quartz properties to it. + * @param schedulerFactory the SchedulerFactory to initialize + */ + private void initSchedulerFactory(StdSchedulerFactory schedulerFactory) throws SchedulerException, IOException { Properties mergedProps = new Properties(); - if (this.resourceLoader != null) { mergedProps.setProperty(StdSchedulerFactory.PROP_SCHED_CLASS_LOAD_HELPER_CLASS, ResourceLoaderClassLoadHelper.class.getName()); @@ -554,17 +541,67 @@ private void initSchedulerFactory(SchedulerFactory schedulerFactory) throws Sche } CollectionUtils.mergePropertiesIntoMap(this.quartzProperties, mergedProps); - if (this.dataSource != null) { mergedProps.put(StdSchedulerFactory.PROP_JOB_STORE_CLASS, LocalDataSourceJobStore.class.getName()); } - - // Make sure to set the scheduler name as configured in the Spring configuration. if (this.schedulerName != null) { mergedProps.put(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, this.schedulerName); } - ((StdSchedulerFactory) schedulerFactory).initialize(mergedProps); + schedulerFactory.initialize(mergedProps); + } + + private Scheduler prepareScheduler(SchedulerFactory schedulerFactory) throws SchedulerException { + if (this.resourceLoader != null) { + // Make given ResourceLoader available for SchedulerFactory configuration. + configTimeResourceLoaderHolder.set(this.resourceLoader); + } + if (this.taskExecutor != null) { + // Make given TaskExecutor available for SchedulerFactory configuration. + configTimeTaskExecutorHolder.set(this.taskExecutor); + } + if (this.dataSource != null) { + // Make given DataSource available for SchedulerFactory configuration. + configTimeDataSourceHolder.set(this.dataSource); + } + if (this.nonTransactionalDataSource != null) { + // Make given non-transactional DataSource available for SchedulerFactory configuration. + configTimeNonTransactionalDataSourceHolder.set(this.nonTransactionalDataSource); + } + + // Get Scheduler instance from SchedulerFactory. + try { + Scheduler scheduler = createScheduler(schedulerFactory, this.schedulerName); + populateSchedulerContext(scheduler); + + if (!this.jobFactorySet && !(scheduler instanceof RemoteScheduler)) { + // Use AdaptableJobFactory as default for a local Scheduler, unless when + // explicitly given a null value through the "jobFactory" bean property. + this.jobFactory = new AdaptableJobFactory(); + } + if (this.jobFactory != null) { + if (this.jobFactory instanceof SchedulerContextAware) { + ((SchedulerContextAware) this.jobFactory).setSchedulerContext(scheduler.getContext()); + } + scheduler.setJobFactory(this.jobFactory); + } + return scheduler; + } + + finally { + if (this.resourceLoader != null) { + configTimeResourceLoaderHolder.remove(); + } + if (this.taskExecutor != null) { + configTimeTaskExecutorHolder.remove(); + } + if (this.dataSource != null) { + configTimeDataSourceHolder.remove(); + } + if (this.nonTransactionalDataSource != null) { + configTimeNonTransactionalDataSourceHolder.remove(); + } + } } /** @@ -586,7 +623,7 @@ protected Scheduler createScheduler(SchedulerFactory schedulerFactory, String sc Thread currentThread = Thread.currentThread(); ClassLoader threadContextClassLoader = currentThread.getContextClassLoader(); boolean overrideClassLoader = (this.resourceLoader != null && - !this.resourceLoader.getClassLoader().equals(threadContextClassLoader)); + this.resourceLoader.getClassLoader() != threadContextClassLoader); if (overrideClassLoader) { currentThread.setContextClassLoader(this.resourceLoader.getClassLoader()); } @@ -618,10 +655,10 @@ protected Scheduler createScheduler(SchedulerFactory schedulerFactory, String sc * Expose the specified context attributes and/or the current * ApplicationContext in the Quartz SchedulerContext. */ - private void populateSchedulerContext() throws SchedulerException { + private void populateSchedulerContext(Scheduler scheduler) throws SchedulerException { // Put specified objects into Scheduler context. if (this.schedulerContextMap != null) { - this.scheduler.getContext().putAll(this.schedulerContextMap); + scheduler.getContext().putAll(this.schedulerContextMap); } // Register ApplicationContext in Scheduler context. @@ -631,7 +668,7 @@ private void populateSchedulerContext() throws SchedulerException { "SchedulerFactoryBean needs to be set up in an ApplicationContext " + "to be able to handle an 'applicationContextSchedulerContextKey'"); } - this.scheduler.getContext().put(this.applicationContextSchedulerContextKey, this.applicationContext); + scheduler.getContext().put(this.applicationContextSchedulerContextKey, this.applicationContext); } } @@ -652,6 +689,8 @@ protected void startScheduler(final Scheduler scheduler, final int startupDelay) logger.info("Will start Quartz Scheduler [" + scheduler.getSchedulerName() + "] in " + startupDelay + " seconds"); } + // Not using the Quartz startDelayed method since we explicitly want a daemon + // thread here, not keeping the JVM alive in case of all other threads ending. Thread schedulerThread = new Thread() { @Override public void run() { @@ -659,6 +698,7 @@ public void run() { Thread.sleep(startupDelay * 1000); } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); // simply proceed } if (logger.isInfoEnabled()) { @@ -695,7 +735,7 @@ public Scheduler getObject() { @Override public Class getObjectType() { - return (this.scheduler != null) ? this.scheduler.getClass() : Scheduler.class; + return (this.scheduler != null ? this.scheduler.getClass() : Scheduler.class); } @Override @@ -762,8 +802,10 @@ public boolean isRunning() throws SchedulingException { */ @Override public void destroy() throws SchedulerException { - logger.info("Shutting down Quartz Scheduler"); - this.scheduler.shutdown(this.waitForJobsToCompleteOnShutdown); + if (this.scheduler != null) { + logger.info("Shutting down Quartz Scheduler"); + this.scheduler.shutdown(this.waitForJobsToCompleteOnShutdown); + } } } diff --git a/spring-context-support/src/main/java/org/springframework/ui/freemarker/FreeMarkerConfigurationFactory.java b/spring-context-support/src/main/java/org/springframework/ui/freemarker/FreeMarkerConfigurationFactory.java index 9cb27d297948..40dac4bbd8e0 100644 --- a/spring-context-support/src/main/java/org/springframework/ui/freemarker/FreeMarkerConfigurationFactory.java +++ b/spring-context-support/src/main/java/org/springframework/ui/freemarker/FreeMarkerConfigurationFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -342,7 +342,7 @@ protected TemplateLoader getTemplateLoaderForPath(String templateLoaderPath) { } return new FileTemplateLoader(file); } - catch (IOException ex) { + catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Cannot resolve template loader path [" + templateLoaderPath + "] to [java.io.File]: using SpringTemplateLoader as fallback", ex); diff --git a/spring-context-support/src/main/resources/org/springframework/mail/javamail/mime.types b/spring-context-support/src/main/resources/org/springframework/mail/javamail/mime.types index 16af62ddcefb..1eabbf5d2f4c 100644 --- a/spring-context-support/src/main/resources/org/springframework/mail/javamail/mime.types +++ b/spring-context-support/src/main/resources/org/springframework/mail/javamail/mime.types @@ -1,5 +1,5 @@ ################################################################################ -# Copyright 2002-2010 the original author or authors. +# Copyright 2002-2018 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,14 +16,15 @@ ################################################################################ # -# Defaults for the Java Activation Framework -# Additional extensions registered in this file: -# text/plain java c c++ pl cc h +# Defaults for the Java Activation Framework (revised). +# Modified extensions registered in this file: +# text/plain java c c++ cpp pl cc h +# image/png png # ################################################################################ text/html html htm HTML HTM -text/plain txt text TXT TEXT java c c++ pl cc h +text/plain txt text TXT TEXT java c c++ cpp pl cc h image/gif gif GIF image/ief ief image/jpeg jpeg jpg jpe JPG @@ -65,11 +66,9 @@ image/x-xbitmap xbm # X-Windows pixelmap (8-bit color) image/x-xpixmap xpm # Portable Network Graphics -image/x-png png +image/png png # Image Exchange Format (RFC 1314) image/ief ief -# JPEG -image/jpeg jpeg jpg jpe # RGB image/rgb rgb # Group III Fax (RFC 1494) diff --git a/spring-test/src/test/java/org/springframework/cache/jcache/JCacheEhCache3AnnotationTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCache3AnnotationTests.java similarity index 95% rename from spring-test/src/test/java/org/springframework/cache/jcache/JCacheEhCache3AnnotationTests.java rename to spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCache3AnnotationTests.java index 86f0f76d0e3e..608eb7bc7cf8 100644 --- a/spring-test/src/test/java/org/springframework/cache/jcache/JCacheEhCache3AnnotationTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCache3AnnotationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-test/src/test/java/org/springframework/cache/jcache/JCacheEhCache3ApiTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCache3ApiTests.java similarity index 95% rename from spring-test/src/test/java/org/springframework/cache/jcache/JCacheEhCache3ApiTests.java rename to spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCache3ApiTests.java index 0e724344f294..97e42e646970 100644 --- a/spring-test/src/test/java/org/springframework/cache/jcache/JCacheEhCache3ApiTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCache3ApiTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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 8b207f0262f6..5e897570a5b8 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-2015 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.springframework.cache.jcache; -import javax.annotation.Resource; import javax.cache.CacheManager; import javax.cache.Caching; import javax.cache.configuration.MutableConfiguration; @@ -26,6 +25,7 @@ import org.junit.Ignore; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.config.AbstractCacheAnnotationTests; @@ -59,7 +59,7 @@ protected ConfigurableApplicationContext getApplicationContext() { } protected CachingProvider getCachingProvider() { - return Caching.getCachingProvider(); + return Caching.getCachingProvider("org.ehcache.jcache.JCacheCachingProvider"); } @After @@ -81,7 +81,7 @@ public void testCustomCacheManager() { @EnableCaching static class EnableCachingConfig extends CachingConfigurerSupport { - @Resource + @Autowired CachingProvider cachingProvider; @Override @@ -93,7 +93,7 @@ public org.springframework.cache.CacheManager cacheManager() { @Bean public CacheManager jCacheManager() { CacheManager cacheManager = this.cachingProvider.getCacheManager(); - MutableConfiguration mutableConfiguration = new MutableConfiguration(); + MutableConfiguration mutableConfiguration = new MutableConfiguration<>(); mutableConfiguration.setStoreByValue(false); // otherwise value has to be Serializable cacheManager.createCache("testCache", mutableConfiguration); cacheManager.createCache("primary", mutableConfiguration); diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCacheApiTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCacheApiTests.java index bfb5bce39f78..ef79b6fc5443 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCacheApiTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCacheApiTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,7 +48,7 @@ public void setup() { } protected CachingProvider getCachingProvider() { - return Caching.getCachingProvider(); + return Caching.getCachingProvider("org.ehcache.jcache.JCacheCachingProvider"); } @After @@ -58,7 +58,6 @@ public void shutdown() { } } - @Override protected JCacheCache getCache() { return this.cache; diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AnnotationCacheOperationSourceTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AnnotationCacheOperationSourceTests.java index 67c4d827704c..6d62634237fe 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AnnotationCacheOperationSourceTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/AnnotationCacheOperationSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,11 +47,11 @@ public class AnnotationCacheOperationSourceTests extends AbstractJCacheTests { private final DefaultJCacheOperationSource source = new DefaultJCacheOperationSource(); - private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); @Before - public void setUp() { + public void setup() { source.setCacheResolver(defaultCacheResolver); source.setExceptionCacheResolver(defaultExceptionCacheResolver); source.setKeyGenerator(defaultKeyGenerator); diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheResolverAdapterTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheResolverAdapterTests.java index b15c215f7e81..f97225b4377e 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheResolverAdapterTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/CacheResolverAdapterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,8 +30,6 @@ import org.springframework.cache.Cache; import org.springframework.cache.jcache.AbstractJCacheTests; -import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; import static org.junit.Assert.*; import static org.mockito.BDDMockito.*; @@ -46,7 +44,7 @@ public class CacheResolverAdapterTests extends AbstractJCacheTests { @Test - public void resolveSimpleCache() { + public void resolveSimpleCache() throws Exception { DefaultCacheInvocationContext dummyContext = createDummyContext(); CacheResolverAdapter adapter = new CacheResolverAdapter(getCacheResolver(dummyContext, "testCache")); Collection caches = adapter.resolveCaches(dummyContext); @@ -56,17 +54,17 @@ public void resolveSimpleCache() { } @Test - public void resolveUnknownCache() { + public void resolveUnknownCache() throws Exception { DefaultCacheInvocationContext dummyContext = createDummyContext(); CacheResolverAdapter adapter = new CacheResolverAdapter(getCacheResolver(dummyContext, null)); - thrown.expect(IllegalArgumentException.class); + thrown.expect(IllegalStateException.class); adapter.resolveCaches(dummyContext); } protected CacheResolver getCacheResolver(CacheInvocationContext context, String cacheName) { CacheResolver cacheResolver = mock(CacheResolver.class); - final javax.cache.Cache cache; + javax.cache.Cache cache; if (cacheName == null) { cache = null; } @@ -78,22 +76,21 @@ protected CacheResolver getCacheResolver(CacheInvocationContext createDummyContext() { - Method method = ReflectionUtils.findMethod(Sample.class, "get", String.class); - Assert.notNull(method); + protected DefaultCacheInvocationContext createDummyContext() throws Exception { + Method method = Sample.class.getMethod("get", String.class); CacheResult cacheAnnotation = method.getAnnotation(CacheResult.class); CacheMethodDetails methodDetails = new DefaultCacheMethodDetails<>(method, cacheAnnotation, "test"); CacheResultOperation operation = new CacheResultOperation(methodDetails, defaultCacheResolver, defaultKeyGenerator, defaultExceptionCacheResolver); - return new DefaultCacheInvocationContext(operation, new Sample(), new Object[] {"id"}); + return new DefaultCacheInvocationContext<>(operation, new Sample(), new Object[] {"id"}); } static class Sample { @CacheResult - private Object get(String id) { + public Object get(String id) { return null; } } 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 54b66033a16b..da4435b1d877 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-2014 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import static org.junit.Assert.*; import static org.mockito.BDDMockito.*; /** @@ -48,60 +49,93 @@ */ public class JCacheErrorHandlerTests { - @Rule - public final ExpectedException thrown = ExpectedException.none(); - private Cache cache; + private Cache errorCache; + private CacheErrorHandler errorHandler; private SimpleService simpleService; + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + @Before public void setup() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class); this.cache = context.getBean("mockCache", Cache.class); + this.errorCache = context.getBean("mockErrorCache", Cache.class); this.errorHandler = context.getBean(CacheErrorHandler.class); this.simpleService = context.getBean(SimpleService.class); } + @Test public void getFail() { UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on get"); Object key = SimpleKeyGenerator.generateKey(0L); - willThrow(exception).given(cache).get(key); + willThrow(exception).given(this.cache).get(key); this.simpleService.get(0L); - verify(errorHandler).handleCacheGetError(exception, cache, key); + verify(this.errorHandler).handleCacheGetError(exception, this.cache, key); + } + + @Test + public void getPutNewElementFail() { + UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on put"); + Object key = SimpleKeyGenerator.generateKey(0L); + given(this.cache.get(key)).willReturn(null); + willThrow(exception).given(this.cache).put(key, 0L); + + this.simpleService.get(0L); + verify(this.errorHandler).handleCachePutError(exception, this.cache, key, 0L); + } + + @Test + public void getFailPutExceptionFail() { + UnsupportedOperationException exceptionOnPut = new UnsupportedOperationException("Test exception on put"); + Object key = SimpleKeyGenerator.generateKey(0L); + given(this.cache.get(key)).willReturn(null); + willThrow(exceptionOnPut).given(this.errorCache).put(key, SimpleService.TEST_EXCEPTION); + + try { + this.simpleService.getFail(0L); + } + catch (IllegalStateException ex) { + assertEquals("Test exception", ex.getMessage()); + } + verify(this.errorHandler).handleCachePutError( + exceptionOnPut, this.errorCache, key, SimpleService.TEST_EXCEPTION); } @Test public void putFail() { UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on put"); Object key = SimpleKeyGenerator.generateKey(0L); - willThrow(exception).given(cache).put(key, 234L); + willThrow(exception).given(this.cache).put(key, 234L); this.simpleService.put(0L, 234L); - verify(errorHandler).handleCachePutError(exception, cache, key, 234L); + verify(this.errorHandler).handleCachePutError(exception, this.cache, key, 234L); } @Test public void evictFail() { UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on evict"); Object key = SimpleKeyGenerator.generateKey(0L); - willThrow(exception).given(cache).evict(key); + willThrow(exception).given(this.cache).evict(key); this.simpleService.evict(0L); - verify(errorHandler).handleCacheEvictError(exception, cache, key); + verify(this.errorHandler).handleCacheEvictError(exception, this.cache, key); } @Test public void clearFail() { UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on evict"); - willThrow(exception).given(cache).clear(); + willThrow(exception).given(this.cache).clear(); this.simpleService.clear(); - verify(errorHandler).handleCacheClearError(exception, cache); + verify(this.errorHandler).handleCacheClearError(exception, this.cache); } @@ -113,7 +147,7 @@ static class Config extends JCacheConfigurerSupport { @Override public CacheManager cacheManager() { SimpleCacheManager cacheManager = new SimpleCacheManager(); - cacheManager.setCaches(Arrays.asList(mockCache())); + cacheManager.setCaches(Arrays.asList(mockCache(), mockErrorCache())); return cacheManager; } @@ -135,15 +169,30 @@ public Cache mockCache() { return cache; } + @Bean + public Cache mockErrorCache() { + Cache cache = mock(Cache.class); + given(cache.getName()).willReturn("error"); + return cache; + } } + @CacheDefaults(cacheName = "test") public static class SimpleService { + + private static final IllegalStateException TEST_EXCEPTION = new IllegalStateException("Test exception"); + private AtomicLong counter = new AtomicLong(); @CacheResult public Object get(long id) { - return counter.getAndIncrement(); + return this.counter.getAndIncrement(); + } + + @CacheResult(exceptionCacheName = "error") + public Object getFail(long id) { + throw TEST_EXCEPTION; } @CachePut @@ -158,4 +207,5 @@ public void evict(long id) { public void clear() { } } + } diff --git a/spring-context-support/src/test/java/org/springframework/cache/transaction/TransactionAwareCacheDecoratorTests.java b/spring-context-support/src/test/java/org/springframework/cache/transaction/TransactionAwareCacheDecoratorTests.java index 151615db55ac..16ee78c9152c 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/transaction/TransactionAwareCacheDecoratorTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/transaction/TransactionAwareCacheDecoratorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,10 +42,17 @@ public class TransactionAwareCacheDecoratorTests { @Test public void createWithNullTarget() { - thrown.expect(IllegalArgumentException.class); + this.thrown.expect(IllegalArgumentException.class); new TransactionAwareCacheDecorator(null); } + @Test + public void getTargetCache() { + Cache target = new ConcurrentMapCache("testCache"); + TransactionAwareCacheDecorator cache = new TransactionAwareCacheDecorator(target); + assertSame(target, cache.getTargetCache()); + } + @Test public void regularOperationsOnTarget() { Cache target = new ConcurrentMapCache("testCache"); @@ -77,13 +84,13 @@ public void putTransactional() { Cache target = new ConcurrentMapCache("testCache"); Cache cache = new TransactionAwareCacheDecorator(target); - TransactionStatus status = txManager.getTransaction(new DefaultTransactionAttribute( - TransactionDefinition.PROPAGATION_REQUIRED)); + TransactionStatus status = this.txManager.getTransaction( + new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED)); Object key = new Object(); cache.put(key, "123"); assertNull(target.get(key)); - txManager.commit(status); + this.txManager.commit(status); assertEquals("123", target.get(key, String.class)); } @@ -119,11 +126,11 @@ public void evictTransactional() { cache.put(key, "123"); - TransactionStatus status = txManager.getTransaction(new DefaultTransactionAttribute( - TransactionDefinition.PROPAGATION_REQUIRED)); + TransactionStatus status = this.txManager.getTransaction( + new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED)); cache.evict(key); assertEquals("123", target.get(key, String.class)); - txManager.commit(status); + this.txManager.commit(status); assertNull(target.get(key)); } @@ -147,11 +154,11 @@ public void clearTransactional() { cache.put(key, "123"); - TransactionStatus status = txManager.getTransaction(new DefaultTransactionAttribute( - TransactionDefinition.PROPAGATION_REQUIRED)); + TransactionStatus status = this.txManager.getTransaction( + new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED)); cache.clear(); assertEquals("123", target.get(key, String.class)); - txManager.commit(status); + this.txManager.commit(status); assertNull(target.get(key)); } diff --git a/spring-context-support/src/test/resources/org/springframework/scheduling/quartz/quartz-hsql.sql b/spring-context-support/src/test/resources/org/springframework/scheduling/quartz/quartz-hsql.sql index 9f8b9d4788fb..57e05e860a32 100644 --- a/spring-context-support/src/test/resources/org/springframework/scheduling/quartz/quartz-hsql.sql +++ b/spring-context-support/src/test/resources/org/springframework/scheduling/quartz/quartz-hsql.sql @@ -2,7 +2,7 @@ -- In your Quartz properties file, you'll need to set -- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.HSQLDBDelegate -- --- Column lenghts are only suggestions. For names, groups, use at least 40 chars. +-- Column lengths are only suggestions. For names, groups, use at least 40 chars. -- for blobs (VARBINARY) use a size that is sure to meet the needs of the amount of data -- you place in job data maps, etc.. -- diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/AnnotationCacheOperationSource.java b/spring-context/src/main/java/org/springframework/cache/annotation/AnnotationCacheOperationSource.java index 438c8e633fe4..0737feba425c 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/AnnotationCacheOperationSource.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/AnnotationCacheOperationSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,8 +43,7 @@ * @since 3.1 */ @SuppressWarnings("serial") -public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperationSource - implements Serializable { +public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperationSource implements Serializable { private final boolean publicMethodsOnly; @@ -129,10 +128,9 @@ public Collection getCacheOperations(CacheAnnotationParser parse /** * Determine the cache operation(s) for the given {@link CacheOperationProvider}. *

This implementation delegates to configured - * {@link CacheAnnotationParser}s for parsing known annotations into - * Spring's metadata attribute class. - *

Can be overridden to support custom annotations that carry - * caching metadata. + * {@link CacheAnnotationParser CacheAnnotationParsers} + * for parsing known annotations into Spring's metadata attribute class. + *

Can be overridden to support custom annotations that carry caching metadata. * @param provider the cache operation provider to use * @return the configured caching operations, or {@code null} if none found */ @@ -177,6 +175,7 @@ public int hashCode() { return this.annotationParsers.hashCode(); } + /** * Callback interface providing {@link CacheOperation} instance(s) based on * a given {@link CacheAnnotationParser}. @@ -184,10 +183,9 @@ public int hashCode() { protected interface CacheOperationProvider { /** - * Returns the {@link CacheOperation} instance(s) provided by the specified parser. - * + * Return the {@link CacheOperation} instance(s) provided by the specified parser. * @param parser the parser to use - * @return the cache operations or {@code null} if none is found + * @return the cache operations, or {@code null} if none found */ Collection getCacheOperations(CacheAnnotationParser parser); } diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java b/spring-context/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java index 80bafb871a72..cb06322f3465 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,39 +23,38 @@ /** * Strategy interface for parsing known caching annotation types. - * {@link AnnotationCacheOperationSource} delegates to such - * parsers for supporting specific annotation types such as Spring's own - * {@link Cacheable}, {@link CachePut} or {@link CacheEvict}. + * {@link AnnotationCacheOperationSource} delegates to such parsers + * for supporting specific annotation types such as Spring's own + * {@link Cacheable}, {@link CachePut} and{@link CacheEvict}. * * @author Costin Leau * @author Stephane Nicoll * @since 3.1 + * @see AnnotationCacheOperationSource + * @see SpringCacheAnnotationParser */ public interface CacheAnnotationParser { /** - * Parses the cache definition for the given class, - * based on a known annotation type. - *

This essentially parses a known cache annotation into Spring's - * metadata attribute class. Returns {@code null} if the class - * is not cacheable. + * Parse the cache definition for the given class, + * based on an annotation type understood by this parser. + *

This essentially parses a known cache annotation into Spring's metadata + * attribute class. Returns {@code null} if the class is not cacheable. * @param type the annotated class - * @return CacheOperation the configured caching operation, - * or {@code null} if none was found + * @return the configured caching operation, or {@code null} if none found * @see AnnotationCacheOperationSource#findCacheOperations(Class) */ Collection parseCacheAnnotations(Class type); /** - * Parses the cache definition for the given method, - * based on a known annotation type. - *

This essentially parses a known cache annotation into Spring's - * metadata attribute class. Returns {@code null} if the method - * is not cacheable. + * Parse the cache definition for the given method, + * based on an annotation type understood by this parser. + *

This essentially parses a known cache annotation into Spring's metadata + * attribute class. Returns {@code null} if the method is not cacheable. * @param method the annotated method - * @return CacheOperation the configured caching operation, - * or {@code null} if none was found + * @return the configured caching operation, or {@code null} if none found * @see AnnotationCacheOperationSource#findCacheOperations(Method) */ Collection parseCacheAnnotations(Method method); + } diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java b/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java index f12d2119ad93..9364e800f250 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java @@ -69,7 +69,9 @@ * following meta-data: *