diff --git a/.gitignore b/.gitignore index b1e4ec06c7..500d63a6c6 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ spring-*/src/main/java/META-INF/MANIFEST.MF *.iml *.ipr *.iws +.idea out test-output atlassian-ide-plugin.xml diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000000..c0ca085f71 --- /dev/null +++ b/.mailmap @@ -0,0 +1,14 @@ +Juergen Hoeller jhoeller + + + + + + + + + + + + +Nick Williams Nicholas Williams diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 02d0bc12e2..7cdf6550dc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,14 +19,6 @@ in our [JIRA issue tracker][] to see if you can find something similar. If not, please create a new issue before submitting a pull request unless the change is truly trivial, e.g. typo fixes, removing compiler warnings, etc. -## Discuss non-trivial contribution ideas with committers - -If you're considering anything more than correcting a typo or fixing a minor -bug, please discuss it on the [spring-framework-contrib][] mailing list before -submitting a pull request. We're happy to provide guidance, but please spend an -hour or two researching the subject on your own including searching the mailing -list for prior discussions. - ## Sign the Contributor License Agreement If you have not previously done so, please fill out and submit the diff --git a/build.gradle b/build.gradle index 3f6d91ff22..cc5eb03d62 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,16 @@ buildscript { repositories { - maven { url "http://repo.springsource.org/plugins-release" } + maven { url "https://repo.spring.io/plugins-release" } } dependencies { - classpath("org.springframework.build.gradle:propdeps-plugin:0.0.3") - classpath("org.springframework.build.gradle:docbook-reference-plugin:0.2.6") + classpath("org.springframework.build.gradle:propdeps-plugin:0.0.7") + classpath("io.spring.gradle:docbook-reference-plugin:0.3.1") + } +} + +ext { + moduleProjects = subprojects.findAll { + !it.name.equals('spring-build-src') && !it.name.equals('spring-framework-bom') } } @@ -12,10 +18,10 @@ configure(allprojects) { project -> group = "org.springframework" version = qualifyVersionIfNecessary(version) - ext.aspectjVersion = "1.7.2" + ext.aspectjVersion = "1.7.4" ext.hsqldbVersion = "1.8.0.10" ext.junitVersion = "4.11" - ext.slf4jVersion = "1.6.1" + ext.slf4jVersion = "1.6.6" ext.gradleScriptDir = "${rootProject.projectDir}/gradle" apply plugin: "propdeps" @@ -58,10 +64,17 @@ configure(allprojects) { project -> test { systemProperty("java.awt.headless", "true") systemProperty("testGroups", project.properties.get("testGroups")) + scanForTestClasses = false + // Do not include "**/*Test.class" since some *Test classes are broken. + include(["**/*Tests.class"]) + // Since we set scanForTestClasses to false, we need to filter out inner + // classes with the "$" pattern; otherwise, using -Dtest.single=MyTests to + // run MyTests by itself will fail if MyTests contains any inner classes. + exclude(["**/Abstract*.class", '**/*$*']) } repositories { - maven { url "http://repo.springsource.org/libs-release" } + maven { url "https://repo.spring.io/libs-release" } } dependencies { @@ -71,25 +84,28 @@ configure(allprojects) { project -> } ext.javadocLinks = [ - "http://docs.oracle.com/javase/6/docs/api", - "http://docs.oracle.com/javaee/6/api", - "http://portals.apache.org/pluto/portlet-2.0-apidocs/", - "http://commons.apache.org/proper/commons-lang/javadocs/api-release/", - "http://commons.apache.org/proper/commons-codec/apidocs/", + "http://docs.oracle.com/javase/7/docs/api/", + "http://docs.oracle.com/javaee/6/api/", + "http://docs.oracle.com/cd/E13222_01/wls/docs90/javadocs/", // CommonJ + "http://pic.dhe.ibm.com/infocenter/wasinfo/v7r0/topic/com.ibm.websphere.javadoc.doc/web/apidocs/", + "http://glassfish.java.net/nonav/docs/v3/api/", "http://docs.jboss.org/jbossas/javadoc/4.0.5/connector/", "http://docs.jboss.org/jbossas/javadoc/7.1.2.Final/", + "http://commons.apache.org/proper/commons-lang/javadocs/api-2.5/", + "http://commons.apache.org/proper/commons-codec/apidocs/", + "http://commons.apache.org/proper/commons-dbcp/apidocs/", + "http://portals.apache.org/pluto/portlet-2.0-apidocs/", + "http://tiles.apache.org/tiles-request/apidocs/", + "http://tiles.apache.org/framework/apidocs/", "http://aopalliance.sourceforge.net/doc/", - "http://glassfish.java.net/nonav/docs/v3/api/", - "http://docs.oracle.com/cd/E13222_01/wls/docs90/javadocs/", // commonj - "http://quartz-scheduler.org/api/2.1.5/", "http://www.eclipse.org/aspectj/doc/released/aspectj5rt-api/", - "http://hc.apache.org/httpclient-3.x/apidocs/", - "http://fasterxml.github.com/jackson-core/javadoc/2.0.0/", - "http://jackson.codehaus.org/1.4.2/javadoc/", - "http://pic.dhe.ibm.com/infocenter/wasinfo/v7r0/topic/com.ibm.websphere.javadoc.doc/web/apidocs/", - "http://ibatis.apache.org/docs/java/dev/", - "http://tiles.apache.org/framework/apidocs/", - "http://commons.apache.org/proper/commons-dbcp/apidocs/", + "http://ehcache.org/apidocs/", + "http://quartz-scheduler.org/api/2.2.0/", + "http://jackson.codehaus.org/1.9.4/javadoc/", + "http://fasterxml.github.com/jackson-core/javadoc/2.3.0/", + "http://fasterxml.github.com/jackson-databind/javadoc/2.3.0/", + "http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs", + "http://ibatis.apache.org/docs/java/dev/" ] as String[] } @@ -118,6 +134,9 @@ configure(subprojects - project(":spring-build-src")) { subproject -> options.author = true options.header = project.name options.links(project.ext.javadocLinks) + if (JavaVersion.current().isJava8Compatible()) { + options.addStringOption('Xdoclint:none', '-quiet') + } // suppress warnings due to cross-module @see and @link references; // note that global 'api' task does display all warnings. @@ -125,10 +144,10 @@ configure(subprojects - project(":spring-build-src")) { subproject -> logging.captureStandardOutput LogLevel.INFO // suppress "## warnings" message } - task sourcesJar(type: Jar, dependsOn:classes) { - classifier = "sources" - from sourceSets.main.allJava.srcDirs - include "**/*.java", "**/*.aj" + task sourcesJar(type: Jar, dependsOn: classes) { + classifier = 'sources' + from sourceSets.main.allSource + // don't include or exclude anything explicitly by default. See SPR-12085. } task javadocJar(type: Jar) { @@ -148,7 +167,7 @@ project("spring-build-src") { dependencies { compile gradleApi() - groovy localGroovy() + compile localGroovy() } configurations.archives.artifacts.clear() @@ -157,14 +176,14 @@ project("spring-build-src") { project("spring-core") { description = "Spring Core" - // As of Spring 3.2 spring-core repackages both asm 4.0 and cglib 3.0 and inlines both - // into the spring-core jar. cglib 3.0 itself depends on asm 4.0, and is therefore + // As of Spring 3.2.9, spring-core repackages asm 5.0 and cglib 3.1, inlining both + // into the spring-core jar. cglib 3.1 itself depends on asm 4+, and is therefore // further transformed by the JarJar task to depend on org.springframework.asm; this // avoids including two different copies of asm unnecessarily. If however future cglib // versions drift from the version of asm used by Spring internally, this duplication // will become necessary. - def asmVersion = "4.0" - def cglibVersion = "3.0" + def asmVersion = "5.0.4" + def cglibVersion = "3.1" configurations { jarjar @@ -219,10 +238,11 @@ project("spring-core") { cglib("cglib:cglib:${cglibVersion}@jar") jarjar("com.googlecode.jarjar:jarjar:1.3") + compile(files(cglibRepackJar)) compile(files(asmRepackJar)) - compile("commons-logging:commons-logging:1.1.1") + compile("commons-logging:commons-logging:1.1.3") optional("org.aspectj:aspectjweaver:${aspectjVersion}") - optional("net.sf.jopt-simple:jopt-simple:3.0") + optional("net.sf.jopt-simple:jopt-simple:3.3") optional("log4j:log4j:1.2.17") testCompile("xmlunit:xmlunit:1.3") testCompile("org.codehaus.woodstox:wstx-asl:3.2.7") { @@ -245,30 +265,33 @@ project("spring-core") { project("spring-beans") { description = "Spring Beans" + dependencies { compile(project(":spring-core")) compile(files(project(":spring-core").cglibRepackJar)) - provided("javax.el:el-api:1.0") - provided("javax.inject:javax.inject:1") + optional("javax.el:el-api:1.0") + optional("javax.inject:javax.inject:1") testCompile("log4j:log4j:1.2.17") } } project("spring-aop") { description = "Spring AOP" + dependencies { + compile(project(":spring-beans")) compile(project(":spring-core")) compile(files(project(":spring-core").cglibRepackJar)) - compile(project(":spring-beans")) compile("aopalliance:aopalliance:1.0") - optional("com.jamonapi:jamon:2.4") - optional("commons-pool:commons-pool:1.5.3") optional("org.aspectj:aspectjweaver:${aspectjVersion}") + optional("commons-pool:commons-pool:1.5.7") + optional("com.jamonapi:jamon:2.4") } } project("spring-expression") { description = "Spring Expression Language (SpEL)" + dependencies { compile(project(":spring-core")) } @@ -276,17 +299,19 @@ project("spring-expression") { project("spring-instrument") { description = "Spring Instrument" - dependencies { - compile(project(":spring-core")) - } + jar { manifest.attributes["Premain-Class"] = "org.springframework.instrument.InstrumentationSavingAgent" + manifest.attributes["Can-Redefine-Classes"] = "true" + manifest.attributes["Can-Retransform-Classes"] = "true" + manifest.attributes["Can-Set-Native-Method-Prefix"] = "false" } } project("spring-instrument-tomcat") { description = "Spring Instrument Tomcat" + dependencies { provided("org.apache.tomcat:catalina:6.0.16") } @@ -294,31 +319,34 @@ project("spring-instrument-tomcat") { project("spring-context") { description = "Spring Context" + dependencies { - optional(project(":spring-instrument")) compile(project(":spring-aop")) compile(project(":spring-beans")) compile(project(":spring-expression")) compile(project(":spring-core")) compile(files(project(":spring-core").cglibRepackJar)) - optional("backport-util-concurrent:backport-util-concurrent:3.0") - optional("javax.ejb:ejb-api:3.0") + optional(project(":spring-instrument")) optional("javax.inject:javax.inject:1") + optional("javax.ejb:ejb-api:3.0") optional("org.apache.geronimo.specs:geronimo-jms_1.1_spec:1.1") + optional("backport-util-concurrent:backport-util-concurrent:3.0") optional("javax.persistence:persistence-api:1.0") optional("javax.validation:validation-api:1.0.0.GA") - optional("org.beanshell:bsh:2.0b4") - optional("org.codehaus.groovy:groovy-all:1.8.8") - optional("org.jruby:jruby:1.6.5.1") - optional("joda-time:joda-time:2.1") - optional("org.slf4j:slf4j-api:${slf4jVersion}") - optional("org.hibernate:hibernate-validator:4.3.0.Final") + optional("org.hibernate:hibernate-validator:4.3.1.Final") + optional("joda-time:joda-time:2.2") optional("org.aspectj:aspectjweaver:${aspectjVersion}") - optional("org.apache.geronimo.specs:geronimo-jta_1.1_spec:1.1") - testCompile("commons-dbcp:commons-dbcp:1.2.2") + optional("org.codehaus.groovy:groovy-all:1.8.9") + optional("org.beanshell:bsh:2.0b4") + optional("org.jruby:jruby:1.7.12") testCompile("javax.inject:javax.inject-tck:1") + testCompile("commons-dbcp:commons-dbcp:1.3") + testCompile("org.slf4j:slf4j-api:${slf4jVersion}") } + // pick up RmiInvocationWrapperRTD.xml in src/main + sourceSets.main.resources.srcDirs += "src/main/java" + test { jvmArgs = ["-disableassertions:org.aspectj.weaver.UnresolvedType"] // SPR-7989 } @@ -326,18 +354,19 @@ project("spring-context") { project("spring-tx") { description = "Spring Transaction" + dependencies { - optional(project(":spring-context")) // for JCA, @EnableTransactionManagement - optional(project(":spring-aop")) compile(project(":spring-beans")) compile(project(":spring-core")) - compile("aopalliance:aopalliance:1.0") - provided("com.ibm.websphere:uow:6.0.2.17") - optional("javax.resource:connector-api:1.5") + optional(project(":spring-aop")) + optional(project(":spring-context")) // for JCA, @EnableTransactionManagement + optional("aopalliance:aopalliance:1.0") optional("org.apache.geronimo.specs:geronimo-jta_1.1_spec:1.1") + optional("javax.resource:connector-api:1.5") optional("javax.ejb:ejb-api:3.0") - testCompile("javax.persistence:persistence-api:1.0") + optional("com.ibm.websphere:uow:6.0.2.17") testCompile("org.aspectj:aspectjweaver:${aspectjVersion}") + testCompile("javax.persistence:persistence-api:1.0") } } @@ -355,16 +384,24 @@ project("spring-oxm") { dependencies { compile(project(":spring-beans")) compile(project(":spring-core")) - optional(project(":spring-context")) // for Jaxb2Marshaller - compile("commons-lang:commons-lang:2.5") - optional("com.thoughtworks.xstream:xstream:1.3.1") - optional("com.sun.xml.bind:jaxb-impl:2.1.7") - optional("org.jibx:jibx-run:1.2.3") - optional("org.apache.xmlbeans:xmlbeans:2.4.0") - optional("org.codehaus.castor:castor-xml:1.3.2") - testCompile("org.codehaus.jettison:jettison:1.0.1") + optional("org.codehaus.castor:castor-xml:1.3.3") { + 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:1.4.7") { + exclude group: 'xpp3', module: 'xpp3_min' + exclude group: 'xmlpull', module: 'xmlpull' + } + optional("org.jibx:jibx-run:1.2.5") + testCompile(project(":spring-context")) testCompile("xmlunit:xmlunit:1.3") testCompile("xmlpull:xmlpull:1.1.3.4a") + testCompile("org.codehaus.jettison:jettison:1.0.1") { + exclude group: 'stax', module: 'stax-api' + } testCompile(files(genCastor.classesDir).builtBy(genCastor)) testCompile(files(genJaxb.classesDir).builtBy(genJaxb)) testCompile(files(genXmlbeans.classesDir).builtBy(genXmlbeans)) @@ -373,61 +410,64 @@ project("spring-oxm") { project("spring-jms") { description = "Spring JMS" + dependencies { compile(project(":spring-core")) compile(project(":spring-beans")) compile(project(":spring-aop")) compile(project(":spring-context")) compile(project(":spring-tx")) - optional(project(":spring-oxm")) - compile("aopalliance:aopalliance:1.0") provided("org.apache.geronimo.specs:geronimo-jms_1.1_spec:1.1") + optional(project(":spring-oxm")) + optional("aopalliance:aopalliance:1.0") optional("org.apache.geronimo.specs:geronimo-jta_1.1_spec:1.1") optional("javax.resource:connector-api:1.5") - optional("org.codehaus.jackson:jackson-mapper-asl:1.4.2") - optional("com.fasterxml.jackson.core:jackson-databind:2.0.1") + optional("org.codehaus.jackson:jackson-mapper-asl:1.7.9") + optional("com.fasterxml.jackson.core:jackson-databind:2.0.6") } } project("spring-jdbc") { description = "Spring JDBC" + dependencies { - compile(project(":spring-core")) compile(project(":spring-beans")) - optional(project(":spring-context")) // for JndiDataSourceLookup + compile(project(":spring-core")) compile(project(":spring-tx")) + optional(project(":spring-context")) // for JndiDataSourceLookup + optional("org.apache.geronimo.specs:geronimo-jta_1.1_spec:1.1") optional("c3p0:c3p0:0.9.1.2") optional("hsqldb:hsqldb:${hsqldbVersion}") - optional("com.h2database:h2:1.0.71") + optional("com.h2database:h2:1.0.79") optional("org.apache.derby:derby:10.5.3.0_1") optional("org.apache.derby:derbyclient:10.5.3.0_1") - optional("org.apache.geronimo.specs:geronimo-jta_1.1_spec:1.1") } } project("spring-context-support") { description = "Spring Context Support" + dependencies { compile(project(":spring-core")) compile(project(":spring-beans")) compile(project(":spring-context")) - optional(project(":spring-jdbc")) // for Quartz support - optional(project(":spring-tx")) // for Quartz support - optional("javax.mail:mail:1.4") + provided("javax.activation:activation:1.1") + optional(project(":spring-jdbc")) // for Quartz support + optional(project(":spring-tx")) // for Quartz support + optional("javax.mail:mail:1.4.7") optional("javax.cache:cache-api:0.5") - optional("net.sf.ehcache:ehcache-core:2.0.0") - optional("opensymphony:quartz:1.6.2") + optional("net.sf.ehcache:ehcache-core:2.0.1") + optional("org.quartz-scheduler:quartz:1.7.3") optional("org.codehaus.fabric3.api:commonj:1.1.0") optional("velocity:velocity:1.5") - optional("org.freemarker:freemarker:2.3.15") + optional("org.freemarker:freemarker:2.3.20") optional("com.lowagie:itext:2.1.7") - optional("jasperreports:jasperreports:2.0.5") - optional("org.slf4j:slf4j-api:${slf4jVersion}") - provided("javax.activation:activation:1.1") - testCompile("org.apache.poi:poi:3.0.2-FINAL") - testCompile("commons-beanutils:commons-beanutils:1.8.0") // for Velocity/JasperReports - testCompile("commons-digester:commons-digester:1.8.1") // for Velocity/JasperReports + optional("net.sf.jasperreports:jasperreports:3.7.6") + testCompile("org.apache.poi:poi:3.6") + testCompile("commons-beanutils:commons-beanutils:1.8.0") // for Velocity/JasperReports + testCompile("commons-digester:commons-digester:1.8.1") // for Velocity/JasperReports testCompile("hsqldb:hsqldb:${hsqldbVersion}") + testCompile("org.slf4j:slf4j-api:${slf4jVersion}") } // pick up **/*.types files in src/main @@ -436,29 +476,30 @@ project("spring-context-support") { project("spring-web") { description = "Spring Web" + dependencies { - compile(project(":spring-core")) - compile(project(":spring-beans")) // for MultiPartFilter - compile(project(":spring-aop")) // for JaxWsPortProxyFactoryBean + compile(project(":spring-aop")) // for JaxWsPortProxyFactoryBean + compile(project(":spring-beans")) // for MultipartFilter compile(project(":spring-context")) - optional(project(":spring-oxm")) // for MarshallingHttpMessageConverter - compile("aopalliance:aopalliance:1.0") - optional("com.caucho:hessian:3.2.1") - optional("rome:rome:1.0") + compile(project(":spring-core")) + provided("javax.servlet:javax.servlet-api:3.0.1") + provided("javax.activation:activation:1.1") + optional(project(":spring-oxm")) // for MarshallingHttpMessageConverter + optional("javax.servlet.jsp:jsp-api:2.1") + optional("javax.portlet:portlet-api:2.0") optional("javax.el:el-api:1.0") optional("javax.faces:jsf-api:1.2_08") - provided("javax.portlet:portlet-api:2.0") - provided("javax.servlet:javax.servlet-api:3.0.1") - provided("javax.servlet.jsp:jsp-api:2.1") optional("javax.xml:jaxrpc-api:1.1") - provided("javax.xml.soap:saaj-api:1.3") - provided("javax.activation:activation:1.1") - optional("commons-fileupload:commons-fileupload:1.2") + optional("javax.xml.soap:saaj-api:1.3") + optional("aopalliance:aopalliance:1.0") + optional("com.caucho:hessian:3.2.1") + optional("commons-fileupload:commons-fileupload:1.3.1") optional("commons-io:commons-io:1.3") optional("commons-httpclient:commons-httpclient:3.1") - optional("org.apache.httpcomponents:httpclient:4.2") - optional("org.codehaus.jackson:jackson-mapper-asl:1.4.2") - optional("com.fasterxml.jackson.core:jackson-databind:2.0.1") + optional("org.apache.httpcomponents:httpclient:4.2.6") + optional("org.codehaus.jackson:jackson-mapper-asl:1.7.9") + optional("com.fasterxml.jackson.core:jackson-databind:2.0.6") + optional("rome:rome:1.0") optional("taglibs:standard:1.1.2") optional("org.eclipse.jetty:jetty-servlet:8.1.5.v20120716") { exclude group: "org.eclipse.jetty.orbit", module: "javax.servlet" @@ -479,85 +520,85 @@ project("spring-orm") { description = "Spring Object/Relational Mapping" compileTestJava { - // necessary to avoid java.lang.VerifyError on toplink compilation - // TODO: remove this block when we remove toplink + // necessary to avoid java.lang.VerifyError on TopLink compilation sourceCompatibility=1.6 targetCompatibility=1.6 } dependencies { - compile("aopalliance:aopalliance:1.0") + compile(project(":spring-beans")) + compile(project(":spring-core")) + compile(project(":spring-jdbc")) + compile(project(":spring-tx")) + optional(project(":spring-aop")) + optional(project(":spring-context")) + optional(project(":spring-web")) + optional("aopalliance:aopalliance:1.0") + optional("javax.persistence:persistence-api:1.0") + optional("org.eclipse.persistence:org.eclipse.persistence.core:1.0.1") + optional("org.eclipse.persistence:org.eclipse.persistence.jpa:1.0.1") optional("org.hibernate:hibernate-core:3.3.2.GA") optional("org.hibernate:hibernate-annotations:3.4.0.GA") optional("org.hibernate:hibernate-entitymanager:3.4.0.GA") optional("org.apache.openjpa:openjpa:1.1.0") - optional("org.eclipse.persistence:org.eclipse.persistence.core:1.0.1") - optional("org.eclipse.persistence:org.eclipse.persistence.jpa:1.0.1") optional("toplink.essentials:toplink-essentials:2.0-41b") optional("javax.jdo:jdo-api:3.0") optional("org.apache.ibatis:ibatis-sqlmap:2.3.4.726") - optional("javax.persistence:persistence-api:1.0") - provided("javax.servlet:servlet-api:2.5") + optional("javax.servlet:servlet-api:2.5") testCompile("javax.servlet:javax.servlet-api:3.0.1") - testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}") - testCompile("commons-dbcp:commons-dbcp:1.2.2") testCompile("org.eclipse.persistence:org.eclipse.persistence.asm:1.0.1") testCompile("org.eclipse.persistence:org.eclipse.persistence.antlr:1.0.1") + testCompile("commons-dbcp:commons-dbcp:1.3") testCompile("hsqldb:hsqldb:${hsqldbVersion}") - compile(project(":spring-core")) - compile(project(":spring-beans")) - optional(project(":spring-aop")) - optional(project(":spring-context")) - compile(project(":spring-tx")) - compile(project(":spring-jdbc")) - optional(project(":spring-web")) + testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}") } } project("spring-orm-hibernate4") { description = "Spring Object/Relational Mapping - Hibernate 4 support" merge.into = project(":spring-orm") + dependencies { - provided(project(":spring-tx")) provided(project(":spring-jdbc")) - optional("org.hibernate:hibernate-core:4.1.0.Final") - optional("org.hibernate:hibernate-entitymanager:4.1.0.Final") + provided(project(":spring-tx")) optional(project(":spring-web")) + optional("org.hibernate:hibernate-core:4.2.21.Final") + optional("org.hibernate:hibernate-entitymanager:4.2.21.Final") optional("javax.servlet:servlet-api:2.5") } } project("spring-webmvc") { description = "Spring Web MVC" + dependencies { + compile(project(":spring-beans")) + compile(project(":spring-context")) compile(project(":spring-core")) compile(project(":spring-expression")) - compile(project(":spring-beans")) compile(project(":spring-web")) - compile(project(":spring-context")) - optional(project(":spring-context-support")) // for Velocity support - optional(project(":spring-oxm")) // for MarshallingView - optional("org.apache.tiles:tiles-api:2.1.2") - optional("org.apache.tiles:tiles-core:2.1.2") - optional("org.apache.tiles:tiles-jsp:2.1.2") - optional("org.apache.tiles:tiles-servlet:2.1.2") + provided("javax.servlet:javax.servlet-api:3.0.1") + optional(project(":spring-context-support")) // for Velocity support + optional(project(":spring-oxm")) // for MarshallingView + optional("javax.servlet.jsp:jsp-api:2.1") + optional("javax.servlet:jstl:1.2") + optional("net.sourceforge.jexcelapi:jxl:2.6.12") + optional("org.apache.poi:poi:3.6") + optional("velocity:velocity:1.5") optional("velocity-tools:velocity-tools-view:1.4") - optional("net.sourceforge.jexcelapi:jxl:2.6.3") - optional("org.apache.poi:poi:3.0.2-FINAL") + optional("org.freemarker:freemarker:2.3.20") optional("com.lowagie:itext:2.1.7") - optional("jasperreports:jasperreports:2.0.5") { + optional("net.sf.jasperreports:jasperreports:3.7.6") { exclude group: "xml-apis", module: "xml-apis" } + optional("org.codehaus.jackson:jackson-mapper-asl:1.7.9") + optional("com.fasterxml.jackson.core:jackson-databind:2.0.6") optional("rome:rome:1.0") - optional("velocity:velocity:1.5") - optional("org.freemarker:freemarker:2.3.15") - optional("org.codehaus.jackson:jackson-mapper-asl:1.4.2") - optional("com.fasterxml.jackson.core:jackson-databind:2.0.1") - provided("javax.servlet:jstl:1.2") - provided("javax.servlet:javax.servlet-api:3.0.1") - provided("javax.servlet.jsp:jsp-api:2.1") + optional("org.apache.tiles:tiles-api:2.1.2") + optional("org.apache.tiles:tiles-core:2.1.2") + optional("org.apache.tiles:tiles-jsp:2.1.2") + optional("org.apache.tiles:tiles-servlet:2.1.2") testCompile(project(":spring-aop")) - testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}") testCompile("rhino:js:1.7R1") testCompile("xmlunit:xmlunit:1.3") testCompile("dom4j:dom4j:1.6.1") { @@ -575,10 +616,11 @@ project("spring-webmvc") { exclude group: "org.eclipse.jetty.orbit", module: "javax.servlet" } testCompile("javax.validation:validation-api:1.0.0.GA") - testCompile("commons-fileupload:commons-fileupload:1.2") + testCompile("org.hibernate:hibernate-validator:4.3.1.Final") + testCompile("org.apache.httpcomponents:httpclient:4.2.6") + testCompile("commons-fileupload:commons-fileupload:1.3.1") testCompile("commons-io:commons-io:1.3") - testCompile("org.hibernate:hibernate-validator:4.3.0.Final") - testCompile("org.apache.httpcomponents:httpclient:4.2") + testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}") } // pick up DispatcherServlet.properties in src/main @@ -588,45 +630,47 @@ project("spring-webmvc") { project("spring-webmvc-tiles3") { description = "Spring Framework Tiles3 Integration" merge.into = project(":spring-webmvc") + dependencies { provided(project(":spring-context")) provided(project(":spring-web")) - provided("javax.el:el-api:1.0") - provided("javax.servlet:jstl:1.2") - provided("javax.servlet.jsp:jsp-api:2.1") - optional("org.apache.tiles:tiles-request-api:1.0.1") - optional("org.apache.tiles:tiles-api:3.0.1") - optional("org.apache.tiles:tiles-core:3.0.1") { + provided("javax.servlet:javax.servlet-api:3.0.1") + optional("javax.servlet.jsp:jsp-api:2.1") + optional("javax.servlet:jstl:1.2") + optional("javax.el:el-api:1.0") + optional("org.apache.tiles:tiles-api:3.0.4") + optional("org.apache.tiles:tiles-core:3.0.4") { exclude group: "org.slf4j", module: "jcl-over-slf4j" } - optional("org.apache.tiles:tiles-servlet:3.0.1") { + optional("org.apache.tiles:tiles-servlet:3.0.4") { exclude group: "org.slf4j", module: "jcl-over-slf4j" } - optional("org.apache.tiles:tiles-jsp:3.0.1") { + optional("org.apache.tiles:tiles-jsp:3.0.4") { exclude group: "org.slf4j", module: "jcl-over-slf4j" } - optional("org.apache.tiles:tiles-extras:3.0.1") { + optional("org.apache.tiles:tiles-el:3.0.4") { exclude group: "org.slf4j", module: "jcl-over-slf4j" } - optional("org.apache.tiles:tiles-el:3.0.1") { + optional("org.apache.tiles:tiles-extras:3.0.4") { exclude group: "org.slf4j", module: "jcl-over-slf4j" + exclude group: "org.springframework", module: "spring-web" } - provided("javax.servlet:javax.servlet-api:3.0.1") testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}") } } project("spring-webmvc-portlet") { description = "Spring Web Portlet" + dependencies { - provided("javax.servlet:servlet-api:2.5") - provided("javax.portlet:portlet-api:2.0") - compile(project(":spring-core")) compile(project(":spring-beans")) compile(project(":spring-context")) + compile(project(":spring-core")) compile(project(":spring-web")) compile(project(":spring-webmvc")) - optional("commons-fileupload:commons-fileupload:1.2") + provided("javax.servlet:servlet-api:2.5") + provided("javax.portlet:portlet-api:2.0") + optional("commons-fileupload:commons-fileupload:1.3.1") } // pick up DispatcherPortlet.properties in src/main @@ -636,23 +680,9 @@ project("spring-webmvc-portlet") { project("spring-test") { description = "Spring TestContext Framework" - task testNG(type: Test) { - useTestNG() - // "TestCase" classes are run by other test classes, not the build. - exclude "**/*TestCase.class" - // Generate TestNG reports alongside JUnit reports. - testReport true - } - - test { - dependsOn testNG - useJUnit() - // "TestCase" classes are run by other test classes, not the build. - exclude(["**/*TestCase.class", "**/*TestSuite.class"]) - } - dependencies { compile(project(":spring-core")) + provided("javax.activation:activation:1.1") optional(project(":spring-beans")) optional(project(":spring-context")) optional(project(":spring-jdbc")) @@ -660,20 +690,42 @@ project("spring-test") { optional(project(":spring-orm")) optional(project(":spring-web")) optional(project(":spring-webmvc")) - optional(project(":spring-webmvc-portlet"), ) + optional(project(":spring-webmvc-portlet")) optional("junit:junit:${junitVersion}") - optional("org.testng:testng:6.5.2") + optional("org.testng:testng:6.8.8") + optional("javax.inject:javax.inject:1") optional("javax.servlet:servlet-api:2.5") optional("javax.servlet.jsp:jsp-api:2.1") + optional("javax.servlet:jstl:1.2") optional("javax.portlet:portlet-api:2.0") optional("javax.persistence:persistence-api:1.0") optional("org.aspectj:aspectjweaver:${aspectjVersion}") testCompile("org.hibernate:hibernate-core:3.3.2.GA") - provided("javax.inject:javax.inject:1") - provided("javax.activation:activation:1.1") - provided("javax.servlet:jstl:1.2") - testCompile "org.slf4j:slf4j-jcl:${slf4jVersion}" testCompile("hsqldb:hsqldb:${hsqldbVersion}") + testCompile "org.slf4j:slf4j-jcl:${slf4jVersion}" + } + + task testNG(type: Test) { + useTestNG() + // forkEvery 1 + scanForTestClasses = false + include "**/testng/**/*.*" + exclude "**/FailingBeforeAndAfterMethodsTests.class" + // "TestCase" classes are run by other test classes, not the build. + exclude "**/*TestCase.class" + // Generate TestNG reports alongside JUnit reports. + getReports().getHtml().setEnabled(true) + // show standard out and standard error of the test JVM(s) on the console + // testLogging.showStandardStreams = true + } + + test { + dependsOn testNG + useJUnit() + exclude "**/testng/**/*.*" + include "**/testng/FailingBeforeAndAfterMethodsTests" + // "TestCase" classes are run by other test classes, not the build. + exclude(["**/*TestCase.class", "**/*TestSuite.class"]) } } @@ -681,33 +733,30 @@ project("spring-test-mvc") { description = "Spring Test MVC Framework" merge.into = project(":spring-test") dependencies { - optional(project(":spring-context")) provided(project(":spring-webmvc")) provided("javax.servlet:javax.servlet-api:3.0.1") + provided("javax.activation:activation:1.1") + optional(project(":spring-context")) optional("org.hamcrest:hamcrest-core:1.3") optional("com.jayway.jsonpath:json-path:0.8.1") optional("xmlunit:xmlunit:1.3") - testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}") - testCompile("javax.servlet:jstl:1.2") - testCompile("org.hibernate:hibernate-validator:4.3.0.Final") - testCompile("org.codehaus.jackson:jackson-mapper-asl:1.4.2") - testCompile("com.fasterxml.jackson.core:jackson-databind:2.0.1") testCompile(project(":spring-context-support")) testCompile(project(":spring-oxm")) - testCompile("com.thoughtworks.xstream:xstream:1.3.1") - testCompile("cglib:cglib-nodep:2.2") + testCompile("javax.servlet:jstl:1.2") + testCompile("javax.mail:mail:1.4.7") + testCompile("org.hibernate:hibernate-validator:4.3.1.Final") + testCompile("com.thoughtworks.xstream:xstream:1.4.7") + testCompile("org.codehaus.jackson:jackson-mapper-asl:1.7.9") + testCompile("com.fasterxml.jackson.core:jackson-databind:2.0.6") testCompile("rome:rome:1.0") - testCompile("javax.activation:activation:1.1") - testCompile("javax.mail:mail:1.4") - testCompile("javax.xml.bind:jaxb-api:2.2.6") - testCompile("org.apache.tiles:tiles-request-api:1.0.1") - testCompile("org.apache.tiles:tiles-api:3.0.1") - testCompile("org.apache.tiles:tiles-core:3.0.1") { + testCompile("org.apache.tiles:tiles-api:3.0.4") + testCompile("org.apache.tiles:tiles-core:3.0.4") { exclude group: "org.slf4j", module: "jcl-over-slf4j" } - testCompile("org.apache.tiles:tiles-servlet:3.0.1") { + testCompile("org.apache.tiles:tiles-servlet:3.0.4") { exclude group: "org.slf4j", module: "jcl-over-slf4j" } + testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}") } } @@ -730,23 +779,24 @@ project("spring-struts") { project("spring-aspects") { description = "Spring Aspects" apply from: "aspects.gradle" + dependencies { - optional(project(":spring-beans")) // for @Configurable support - optional(project(":spring-aop")) // for @Async support - optional(project(":spring-context")) // for @Enable* support - compile(project(":spring-context-support")) // for JavaMail support - optional(project(":spring-tx")) // for JPA, @Transactional support - optional(project(":spring-orm")) // for JPA exception translation support aspects(project(":spring-orm")) - provided("javax.persistence:persistence-api:1.0") - testCompile("javax.mail:mail:1.4") ajc("org.aspectj:aspectjtools:${aspectjVersion}") rt("org.aspectj:aspectjrt:${aspectjVersion}") compile("org.aspectj:aspectjweaver:${aspectjVersion}") - testCompile(project(":spring-core")) // for CodeStyleAspect - compile(project(":spring-beans")) // for "p" namespace visibility + provided("javax.persistence:persistence-api:1.0") + optional(project(":spring-aop")) // for @Async support + optional(project(":spring-beans")) // for @Configurable support + optional(project(":spring-context")) // for @Enable* support + optional(project(":spring-context-support")) // for JavaMail support + optional(project(":spring-orm")) // for JPA exception translation support + optional(project(":spring-tx")) // for JPA, @Transactional support + testCompile(project(":spring-core")) // for CodeStyleAspect testCompile(project(":spring-test")) + testCompile("javax.mail:mail:1.4.7") } + eclipse.project { natures += "org.eclipse.ajdt.ui.ajnature" buildCommands = [new org.gradle.plugins.ide.eclipse.model. @@ -754,12 +804,48 @@ project("spring-aspects") { } } +project("spring-framework-bom") { + description = "Spring Framework (Bill of Materials)" + + configurations.archives.artifacts.clear() + artifacts { + // work around GRADLE-2406 by attaching text artifact + archives(file("spring-framework-bom.txt")) + } + + install { + repositories.mavenInstaller { + pom.whenConfigured { + packaging = "pom" + withXml { + asNode().children().last() + { + delegate.dependencyManagement { + delegate.dependencies { + parent.subprojects.sort { "$it.name" }.each { p -> + if (p.hasProperty("merge") && p.merge.into == null && p != project) { + delegate.dependency { + delegate.groupId(p.group) + delegate.artifactId(p.name) + delegate.version(p.version) + } + } + } + } + } + } + } + } + } + } +} + configure(rootProject) { description = "Spring Framework" apply plugin: "docbook-reference" apply plugin: "groovy" - apply plugin: "detect-split-packages" + + // apply plugin: "detect-split-packages" apply from: "${gradleScriptDir}/jdiff.gradle" reference { @@ -767,31 +853,33 @@ configure(rootProject) { pdfFilename = "spring-framework-reference.pdf" } - detectSplitPackages { - projectsToScan -= project(":spring-instrument-tomcat") - } + // TODO: DetectSplitPackagesPlugin fails in line 154 due to method not found on java.io.File. + // TODO: Possibly related to user rights or OS differences on OpenJDK 8; works fine on JDK 7. + // detectSplitPackages { + // projectsToScan -= project(":spring-instrument-tomcat") + // } // don't publish the default jar for the root project configurations.archives.artifacts.clear() dependencies { // for integration tests - testCompile(project(":spring-core")) - testCompile(project(":spring-beans")) testCompile(project(":spring-aop")) - testCompile(project(":spring-expression")) + testCompile(project(":spring-beans")) testCompile(project(":spring-context")) - testCompile(project(":spring-tx")) + testCompile(project(":spring-core")) + testCompile(project(":spring-expression")) testCompile(project(":spring-jdbc")) + testCompile(project(":spring-orm")) testCompile(project(":spring-test")) + testCompile(project(":spring-tx")) testCompile(project(":spring-web")) testCompile(project(":spring-webmvc-portlet")) - testCompile(project(":spring-orm")) - testCompile("org.hibernate:hibernate-core:4.1.0.Final") testCompile("javax.servlet:servlet-api:2.5") testCompile("javax.portlet:portlet-api:2.0") testCompile("javax.inject:javax.inject:1") testCompile("javax.resource:connector-api:1.5") testCompile("org.aspectj:aspectjweaver:${aspectjVersion}") + testCompile("org.hibernate:hibernate-core:4.2.21.Final") testCompile("hsqldb:hsqldb:${hsqldbVersion}") } @@ -812,6 +900,9 @@ configure(rootProject) { options.stylesheetFile = file("src/api/stylesheet.css") options.splitIndex = true options.links(project.ext.javadocLinks) + if (JavaVersion.current().isJava8Compatible()) { + options.addStringOption('Xdoclint:none', '-quiet') + } source subprojects.collect { project -> project.sourceSets.main.allJava @@ -822,7 +913,7 @@ 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" }, @@ -833,7 +924,7 @@ configure(rootProject) { } } - task docsZip(type: Zip) { + task docsZip(type: Zip, dependsOn: 'reference') { group = "Distribution" baseName = "spring-framework" classifier = "docs" @@ -859,8 +950,8 @@ configure(rootProject) { classifier = "schema" description = "Builds -${classifier} archive containing all " + "XSDs for deployment at http://springframework.org/schema." - - subprojects.each { subproject -> + duplicatesStrategy 'exclude' + moduleProjects.each { subproject -> def Properties schemas = new Properties(); subproject.sourceSets.main.resources.find { @@ -906,7 +997,7 @@ configure(rootProject) { into "${baseDir}/schema" } - subprojects.each { subproject -> + moduleProjects.each { subproject -> into ("${baseDir}/libs") { from subproject.jar if (subproject.tasks.findByPath("sourcesJar")) { @@ -959,7 +1050,7 @@ configure(rootProject) { task wrapper(type: Wrapper) { description = "Generates gradlew[.bat] scripts" - gradleVersion = "1.3" + gradleVersion = "2.5" doLast() { def gradleOpts = "-XX:MaxPermSize=1024m -Xmx1024m" diff --git a/buildSrc/src/main/groovy/org/springframework/build/gradle/DetectSplitPackagesPlugin.groovy b/buildSrc/src/main/groovy/org/springframework/build/gradle/DetectSplitPackagesPlugin.groovy index ddafa956ef..dc554fb229 100644 --- a/buildSrc/src/main/groovy/org/springframework/build/gradle/DetectSplitPackagesPlugin.groovy +++ b/buildSrc/src/main/groovy/org/springframework/build/gradle/DetectSplitPackagesPlugin.groovy @@ -49,7 +49,7 @@ import org.gradle.api.tasks.TaskAction public class DetectSplitPackagesPlugin implements Plugin { public void apply(Project project) { def tasks = project.tasks - Task detectSplitPackages = tasks.add("detectSplitPackages", DetectSplitPackagesTask.class) + Task detectSplitPackages = tasks.create("detectSplitPackages", DetectSplitPackagesTask.class) if (tasks.asMap.containsKey("check")) { tasks.getByName("check").dependsOn detectSplitPackages } diff --git a/buildSrc/src/main/groovy/org/springframework/build/gradle/MergePlugin.groovy b/buildSrc/src/main/groovy/org/springframework/build/gradle/MergePlugin.groovy index 4143780702..40bbce0282 100644 --- a/buildSrc/src/main/groovy/org/springframework/build/gradle/MergePlugin.groovy +++ b/buildSrc/src/main/groovy/org/springframework/build/gradle/MergePlugin.groovy @@ -21,20 +21,17 @@ import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.ProjectDependency; import org.gradle.api.artifacts.maven.Conf2ScopeMapping import org.gradle.api.plugins.MavenPlugin -import org.gradle.api.tasks.* import org.gradle.plugins.ide.eclipse.EclipsePlugin -import org.gradle.plugins.ide.eclipse.model.EclipseClasspath; import org.gradle.plugins.ide.idea.IdeaPlugin import org.gradle.api.invocation.* - /** * Gradle plugin that allows projects to merged together. Primarily developed to - * allow Spring to support multiple multiple incompatible versions of third-party + * allow Spring to support multiple incompatible versions of third-party * dependencies (for example Hibernate v3 and v4). *

* The 'merge' extension should be used to define how projects are merged, for example: - *

+ * 
  * configure(subprojects) {
  *     apply plugin: MergePlugin
  * }
@@ -67,22 +64,22 @@ class MergePlugin implements Plugin {
 		project.plugins.apply(IdeaPlugin)
 
 		MergeModel model = project.extensions.create("merge", MergeModel)
-		project.configurations.add("merging")
-		Configuration runtimeMerge = project.configurations.add("runtimeMerge")
+		project.configurations.create("merging")
+		Configuration runtimeMerge = project.configurations.create("runtimeMerge")
 
 		// Ensure the IDE can reference merged projects
-		project.eclipse.classpath.plusConfigurations += [runtimeMerge]
-		project.idea.module.scopes.PROVIDED.plus += runtimeMerge
+		project.eclipse.classpath.plusConfigurations += [ runtimeMerge ]
+		project.idea.module.scopes.PROVIDED.plus += [ runtimeMerge ]
 
 		// Hook to perform the actual merge logic
 		project.afterEvaluate{
-			if(it.merge.into != null) {
+			if (it.merge.into != null) {
 				setup(it)
 			}
 		}
 
 		// Hook to build runtimeMerge dependencies
-		if(!attachedProjectsEvaluated) {
+		if (!attachedProjectsEvaluated) {
 			project.gradle.projectsEvaluated{
 				postProcessProjects(it)
 			}
@@ -102,7 +99,7 @@ class MergePlugin implements Plugin {
 		// invoking a task will invoke the task with the same name on 'into' project
 		["sourcesJar", "jar", "javadocJar", "javadoc", "install", "artifactoryPublish"].each {
 			def task = project.tasks.findByPath(it)
-			if(task) {
+			if (task) {
 				task.enabled = false
 				task.dependsOn(project.merge.into.tasks.findByPath(it))
 			}
@@ -120,8 +117,8 @@ class MergePlugin implements Plugin {
 	private void setupMaven(Project project) {
 		project.configurations.each { configuration ->
 			Conf2ScopeMapping mapping = project.conf2ScopeMappings.getMapping([configuration])
-			if(mapping.scope) {
-				Configuration intoConfiguration = project.merge.into.configurations.add(
+			if (mapping.scope) {
+				Configuration intoConfiguration = project.merge.into.configurations.create(
 					project.name + "-" + configuration.name)
 				configuration.excludeRules.each {
 					configuration.exclude([
@@ -131,12 +128,13 @@ class MergePlugin implements Plugin {
 				configuration.dependencies.each {
 					def intoCompile = project.merge.into.configurations.getByName("compile")
 					// Protect against changing a compile scope dependency (SPR-10218)
-					if(!intoCompile.dependencies.contains(it)) {
+					if (!intoCompile.dependencies.contains(it)) {
 						intoConfiguration.dependencies.add(it)
 					}
 				}
+				def index = project.parent.childProjects.findIndexOf {p -> p.getValue() == project}
 				project.merge.into.install.repositories.mavenInstaller.pom.scopeMappings.addMapping(
-					mapping.priority + 100, intoConfiguration, mapping.scope)
+					mapping.priority + 100 + index, intoConfiguration, mapping.scope)
 			}
 		}
 	}
diff --git a/buildSrc/src/main/groovy/org/springframework/build/gradle/TestSourceSetDependenciesPlugin.groovy b/buildSrc/src/main/groovy/org/springframework/build/gradle/TestSourceSetDependenciesPlugin.groovy
index 1d6134ab59..2423bb0242 100644
--- a/buildSrc/src/main/groovy/org/springframework/build/gradle/TestSourceSetDependenciesPlugin.groovy
+++ b/buildSrc/src/main/groovy/org/springframework/build/gradle/TestSourceSetDependenciesPlugin.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2014 the original author 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,6 @@ import org.gradle.api.Project
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.ProjectDependency;
 
-
 /**
  * Gradle plugin that automatically updates testCompile dependencies to include
  * the test source sets of project dependencies.
@@ -41,11 +40,10 @@ class TestSourceSetDependenciesPlugin implements Plugin {
 		}
 	}
 
-	private void collectProjectDependencies(Set projectDependencies,
-			Project project) {
-		for(def configurationName in ["compile", "optional", "provided", "testCompile"]) {
+	private void collectProjectDependencies(Set projectDependencies, Project project) {
+		for (def configurationName in ["compile", "optional", "provided", "testCompile"]) {
 			Configuration configuration = project.getConfigurations().findByName(configurationName)
-			if(configuration) {
+			if (configuration) {
 				configuration.dependencies.findAll { it instanceof ProjectDependency }.each {
 					projectDependencies.add(it)
 					collectProjectDependencies(projectDependencies, it.dependencyProject)
diff --git a/gradle.properties b/gradle.properties
index 4c5915ce80..b0b0c2dcbe 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1 +1 @@
-version=3.2.3.BUILD-SNAPSHOT
+version=3.2.19.BUILD-SNAPSHOT
diff --git a/gradle/ide.gradle b/gradle/ide.gradle
index 3f73282254..66fc228301 100644
--- a/gradle/ide.gradle
+++ b/gradle/ide.gradle
@@ -41,6 +41,17 @@ eclipse.classpath.file.whenMerged { classpath ->
 	}
 }
 
+// Ensure project dependencies come after 3rd-party libs (SPR-11836)
+// https://jira.spring.io/browse/SPR-11836
+eclipse.classpath.file.whenMerged { classpath ->
+	classpath.entries.findAll { it instanceof ProjectDependency }.each {
+		// delete from original position
+		classpath.entries.remove(it)
+		// append to end of classpath
+		classpath.entries.add(it)
+	}
+}
+
 // Allow projects to be used as WPT modules
 eclipse.project.natures "org.eclipse.wst.common.project.facet.core.nature"
 
diff --git a/gradle/publish-maven.gradle b/gradle/publish-maven.gradle
index d47098b4f1..bcc7f4aeb8 100644
--- a/gradle/publish-maven.gradle
+++ b/gradle/publish-maven.gradle
@@ -43,7 +43,7 @@ def customizePom(pom, gradleProject) {
 				developer {
 					id = "jhoeller"
 					name = "Juergen Hoeller"
-					email = "jhoeller@vmware.com"
+					email = "jhoeller@gopivotal.com"
 				}
 			}
 		}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index b6b646b0c0..0087cd3b18 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 8281bd16ba..cdab2f66f1 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Wed Nov 28 09:14:59 PST 2012
+#Wed Apr 30 13:06:57 CEST 2014
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=http\://services.gradle.org/distributions/gradle-1.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-bin.zip
diff --git a/settings.gradle b/settings.gradle
index 8c99e5a179..5a93a86fec 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -22,6 +22,7 @@ include "spring-web"
 include "spring-webmvc"
 include "spring-webmvc-portlet"
 include "spring-webmvc-tiles3"
+include "spring-framework-bom"
 
 // Exposes gradle buildSrc for IDE support
 include "buildSrc"
diff --git a/spring-aop/src/main/java/org/springframework/aop/IntroductionAwareMethodMatcher.java b/spring-aop/src/main/java/org/springframework/aop/IntroductionAwareMethodMatcher.java
index ffefed7eee..ed228bfa20 100644
--- a/spring-aop/src/main/java/org/springframework/aop/IntroductionAwareMethodMatcher.java
+++ b/spring-aop/src/main/java/org/springframework/aop/IntroductionAwareMethodMatcher.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,9 +19,9 @@
 import java.lang.reflect.Method;
 
 /**
- * A specialized type of MethodMatcher that takes into account introductions when
- * matching methods. If there are no introductions on the target class, a method
- * matcher may be able to optimize matching more effectively for example.
+ * A specialized type of {@link MethodMatcher} that takes into account introductions
+ * when matching methods. If there are no introductions on the target class,
+ * a method matcher may be able to optimize matching more effectively for example.
  *
  * @author Adrian Colyer
  * @since 2.0
@@ -39,6 +39,6 @@ public interface IntroductionAwareMethodMatcher extends MethodMatcher {
 	 * asking is the subject on one or more introductions; {@code false} otherwise
 	 * @return whether or not this method matches statically
 	 */
-	boolean matches(Method method, Class targetClass, boolean hasIntroductions);
+	boolean matches(Method method, Class targetClass, boolean hasIntroductions);
 
 }
diff --git a/spring-aop/src/main/java/org/springframework/aop/IntroductionInfo.java b/spring-aop/src/main/java/org/springframework/aop/IntroductionInfo.java
index 6ba69a833d..9f68b07424 100644
--- a/spring-aop/src/main/java/org/springframework/aop/IntroductionInfo.java
+++ b/spring-aop/src/main/java/org/springframework/aop/IntroductionInfo.java
@@ -34,6 +34,6 @@ public interface IntroductionInfo {
 	 * Return the additional interfaces introduced by this Advisor or Advice.
 	 * @return the introduced interfaces
 	 */
-	Class[] getInterfaces();
+	Class[] getInterfaces();
 
 }
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 515358c3e3..06a85f1d02 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-2012 the original author or authors.
+ * Copyright 2002-2014 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -120,9 +120,9 @@ public static JoinPoint currentJoinPoint() {
 	/** Non-null if after returning advice binds the return value */
 	private String returningName = null;
 
-	private Class discoveredReturningType = Object.class;
+	private Class discoveredReturningType = Object.class;
 
-	private Class discoveredThrowingType = Object.class;
+	private Class discoveredThrowingType = Object.class;
 
 	/**
 	 * Index for thisJoinPoint argument (currently only
@@ -238,7 +238,7 @@ public void setArgumentNames(String argNames) {
 		setArgumentNamesFromStringArray(tokens);
 	}
 
-	public void setArgumentNamesFromStringArray(String[] args) {
+	public void setArgumentNamesFromStringArray(String... args) {
 		this.argumentNames = new String[args.length];
 		for (int i = 0; i < args.length; i++) {
 			this.argumentNames[i] = StringUtils.trimWhitespace(args[i]);
@@ -251,7 +251,7 @@ public void setArgumentNamesFromStringArray(String[] args) {
 		if (argumentNames != null) {
 			if (aspectJAdviceMethod.getParameterTypes().length == argumentNames.length + 1) {
 				// May need to add implicit join point arg name...
-				Class firstArgType = aspectJAdviceMethod.getParameterTypes()[0];
+				Class firstArgType = aspectJAdviceMethod.getParameterTypes()[0];
 				if (firstArgType == JoinPoint.class ||
 						firstArgType == ProceedingJoinPoint.class ||
 						firstArgType == JoinPoint.StaticPart.class) {
@@ -290,7 +290,7 @@ protected void setReturningNameNoCheck(String name) {
 		}
 	}
 
-	protected Class getDiscoveredReturningType() {
+	protected Class getDiscoveredReturningType() {
 		return this.discoveredReturningType;
 	}
 
@@ -324,7 +324,7 @@ protected void setThrowingNameNoCheck(String name) {
 		}
 	}
 
-	protected Class getDiscoveredThrowingType() {
+	protected Class getDiscoveredThrowingType() {
 		return this.discoveredThrowingType;
 	}
 
@@ -362,7 +362,7 @@ public synchronized final void calculateArgumentBindings() {
 		}
 
 		int numUnboundArgs = this.adviceInvocationArgumentCount;
-		Class[] parameterTypes = this.aspectJAdviceMethod.getParameterTypes();
+		Class[] parameterTypes = this.aspectJAdviceMethod.getParameterTypes();
 		if (maybeBindJoinPoint(parameterTypes[0]) || maybeBindProceedingJoinPoint(parameterTypes[0])) {
 			numUnboundArgs--;
 		}
@@ -378,7 +378,7 @@ else if (maybeBindJoinPointStaticPart(parameterTypes[0])) {
 		this.argumentsIntrospected = true;
 	}
 
-	private boolean maybeBindJoinPoint(Class candidateParameterType) {
+	private boolean maybeBindJoinPoint(Class candidateParameterType) {
 		if (candidateParameterType.equals(JoinPoint.class)) {
 			this.joinPointArgumentIndex = 0;
 			return true;
@@ -388,7 +388,7 @@ private boolean maybeBindJoinPoint(Class candidateParameterType) {
 		}
 	}
 
-	private boolean maybeBindProceedingJoinPoint(Class candidateParameterType) {
+	private boolean maybeBindProceedingJoinPoint(Class candidateParameterType) {
 		if (candidateParameterType.equals(ProceedingJoinPoint.class)) {
 			if (!supportsProceedingJoinPoint()) {
 				throw new IllegalArgumentException("ProceedingJoinPoint is only supported for around advice");
@@ -405,7 +405,7 @@ protected boolean supportsProceedingJoinPoint() {
 		return false;
 	}
 
-	private boolean maybeBindJoinPointStaticPart(Class candidateParameterType) {
+	private boolean maybeBindJoinPointStaticPart(Class candidateParameterType) {
 		if (candidateParameterType.equals(JoinPoint.StaticPart.class)) {
 			this.joinPointStaticPartArgumentIndex = 0;
 			return true;
@@ -509,8 +509,8 @@ private void configurePointcutParameters(int argumentIndexOffset) {
 			numParametersToRemove++;
 		}
 		String[] pointcutParameterNames = new String[this.argumentNames.length - numParametersToRemove];
-		Class[] pointcutParameterTypes = new Class[pointcutParameterNames.length];
-		Class[] methodParameterTypes = this.aspectJAdviceMethod.getParameterTypes();
+		Class[] pointcutParameterTypes = new Class[pointcutParameterNames.length];
+		Class[] methodParameterTypes = this.aspectJAdviceMethod.getParameterTypes();
 
 		int index = 0;
 		for (int i = 0; i < this.argumentNames.length; i++) {
@@ -678,7 +678,7 @@ public AdviceExcludingMethodMatcher(Method adviceMethod) {
 			this.adviceMethod = adviceMethod;
 		}
 
-		public boolean matches(Method method, Class targetClass) {
+		public boolean matches(Method method, Class targetClass) {
 			return !this.adviceMethod.equals(method);
 		}
 
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 e14f254612..402dfb9ebc 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-2012 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.lang.reflect.Method;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
@@ -29,7 +30,6 @@
 import org.apache.commons.logging.LogFactory;
 import org.aspectj.weaver.BCException;
 import org.aspectj.weaver.patterns.NamePattern;
-import org.aspectj.weaver.reflect.ReflectionWorld;
 import org.aspectj.weaver.reflect.ReflectionWorld.ReflectionWorldException;
 import org.aspectj.weaver.reflect.ShadowMatchImpl;
 import org.aspectj.weaver.tools.ContextBasedMatcher;
@@ -42,6 +42,7 @@
 import org.aspectj.weaver.tools.PointcutParser;
 import org.aspectj.weaver.tools.PointcutPrimitive;
 import org.aspectj.weaver.tools.ShadowMatch;
+
 import org.springframework.aop.ClassFilter;
 import org.springframework.aop.IntroductionAwareMethodMatcher;
 import org.springframework.aop.MethodMatcher;
@@ -55,6 +56,7 @@
 import org.springframework.beans.factory.BeanFactoryUtils;
 import org.springframework.beans.factory.FactoryBean;
 import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.util.ClassUtils;
 import org.springframework.util.ObjectUtils;
 import org.springframework.util.StringUtils;
 
@@ -98,14 +100,16 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
 
 	private static final Log logger = LogFactory.getLog(AspectJExpressionPointcut.class);
 
-	private Class pointcutDeclarationScope;
+	private Class pointcutDeclarationScope;
 
 	private String[] pointcutParameterNames = new String[0];
 
-	private Class[] pointcutParameterTypes = new Class[0];
+	private Class[] pointcutParameterTypes = new Class[0];
 
 	private BeanFactory beanFactory;
 
+	private transient ClassLoader pointcutClassLoader;
+
 	private transient PointcutExpression pointcutExpression;
 
 	private transient Map shadowMatchCache = new ConcurrentHashMap(32);
@@ -123,7 +127,7 @@ public AspectJExpressionPointcut() {
 	 * @param paramNames the parameter names for the pointcut
 	 * @param paramTypes the parameter types for the pointcut
 	 */
-	public AspectJExpressionPointcut(Class declarationScope, String[] paramNames, Class[] paramTypes) {
+	public AspectJExpressionPointcut(Class declarationScope, String[] paramNames, Class[] paramTypes) {
 		this.pointcutDeclarationScope = declarationScope;
 		if (paramNames.length != paramTypes.length) {
 			throw new IllegalStateException(
@@ -137,21 +141,21 @@ public AspectJExpressionPointcut(Class declarationScope, String[] paramNames, Cl
 	/**
 	 * Set the declaration scope for the pointcut.
 	 */
-	public void setPointcutDeclarationScope(Class pointcutDeclarationScope) {
+	public void setPointcutDeclarationScope(Class pointcutDeclarationScope) {
 		this.pointcutDeclarationScope = pointcutDeclarationScope;
 	}
 
 	/**
 	 * Set the parameter names for the pointcut.
 	 */
-	public void setParameterNames(String[] names) {
+	public void setParameterNames(String... names) {
 		this.pointcutParameterNames = names;
 	}
 
 	/**
 	 * Set the parameter types for the pointcut.
 	 */
-	public void setParameterTypes(Class[] types) {
+	public void setParameterTypes(Class... types) {
 		this.pointcutParameterTypes = types;
 	}
 
@@ -180,20 +184,13 @@ private void checkReadyToMatch() {
 			throw new IllegalStateException("Must set property 'expression' before attempting to match");
 		}
 		if (this.pointcutExpression == null) {
-			this.pointcutExpression = buildPointcutExpression();
+			this.pointcutClassLoader = (this.beanFactory instanceof ConfigurableBeanFactory ?
+					((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader() :
+					ClassUtils.getDefaultClassLoader());
+			this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
 		}
 	}
 
-	/**
-	 * Build the underlying AspectJ pointcut expression.
-	 */
-	private PointcutExpression buildPointcutExpression() {
-		ClassLoader cl = (this.beanFactory instanceof ConfigurableBeanFactory ? ((ConfigurableBeanFactory) this.beanFactory)
-				.getBeanClassLoader() : Thread.currentThread()
-				.getContextClassLoader());
-		return buildPointcutExpression(cl);
-	}
-
 	/**
 	 * Build the underlying AspectJ pointcut expression.
 	 */
@@ -202,11 +199,9 @@ private PointcutExpression buildPointcutExpression(ClassLoader classLoader) {
 		PointcutParameter[] pointcutParameters = new PointcutParameter[this.pointcutParameterNames.length];
 		for (int i = 0; i < pointcutParameters.length; i++) {
 			pointcutParameters[i] = parser.createPointcutParameter(
-					this.pointcutParameterNames[i],
-					this.pointcutParameterTypes[i]);
+					this.pointcutParameterNames[i], this.pointcutParameterTypes[i]);
 		}
-		return parser.parsePointcutExpression(
-				replaceBooleanOperators(getExpression()),
+		return parser.parsePointcutExpression(replaceBooleanOperators(getExpression()),
 				this.pointcutDeclarationScope, pointcutParameters);
 	}
 
@@ -244,30 +239,28 @@ public PointcutExpression getPointcutExpression() {
 		return this.pointcutExpression;
 	}
 
-	public boolean matches(Class targetClass) {
+	public boolean matches(Class targetClass) {
 		checkReadyToMatch();
 		try {
-			return this.pointcutExpression.couldMatchJoinPointsInType(targetClass);
-		} catch (ReflectionWorldException e) {
-			logger.debug("PointcutExpression matching rejected target class", e);
 			try {
-				// Actually this is still a "maybe" - treat the pointcut as dynamic if we
-				// don't know enough yet
-				return getFallbackPointcutExpression(targetClass).couldMatchJoinPointsInType(targetClass);
-			} catch (BCException ex) {
-				logger.debug(
-						"Fallback PointcutExpression matching rejected target class",
-						ex);
-				return false;
+				return this.pointcutExpression.couldMatchJoinPointsInType(targetClass);
+			}
+			catch (ReflectionWorldException ex) {
+				logger.debug("PointcutExpression matching rejected target class - trying fallback expression", ex);
+				// Actually this is still a "maybe" - treat the pointcut as dynamic if we don't know enough yet
+				PointcutExpression fallbackExpression = getFallbackPointcutExpression(targetClass);
+				if (fallbackExpression != null) {
+					return fallbackExpression.couldMatchJoinPointsInType(targetClass);
+				}
 			}
 		}
 		catch (BCException ex) {
 			logger.debug("PointcutExpression matching rejected target class", ex);
-			return false;
 		}
+		return false;
 	}
 
-	public boolean matches(Method method, Class targetClass, boolean beanHasIntroductions) {
+	public boolean matches(Method method, Class targetClass, boolean beanHasIntroductions) {
 		checkReadyToMatch();
 		Method targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
 		ShadowMatch shadowMatch = getShadowMatch(targetMethod, method);
@@ -283,11 +276,19 @@ else if (shadowMatch.neverMatches()) {
 		}
 		else {
 			// the maybe case
-			return (beanHasIntroductions || matchesIgnoringSubtypes(shadowMatch) || matchesTarget(shadowMatch, targetClass));
+			if (beanHasIntroductions) {
+				return true;
+			}
+			// A match test returned maybe - if there are any subtype sensitive variables
+			// involved in the test (this, target, at_this, at_target, at_annotation) then
+			// we say this is not a match as in Spring there will never be a different
+			// runtime subtype.
+			RuntimeTestWalker walker = getRuntimeTestWalker(shadowMatch);
+			return (!walker.testsSubtypeSensitiveVars() || walker.testTargetInstanceOfResidue(targetClass));
 		}
 	}
 
-	public boolean matches(Method method, Class targetClass) {
+	public boolean matches(Method method, Class targetClass) {
 		return matches(method, targetClass, false);
 	}
 
@@ -296,7 +297,7 @@ public boolean isRuntime() {
 		return this.pointcutExpression.mayNeedDynamicTest();
 	}
 
-	public boolean matches(Method method, Class targetClass, Object[] args) {
+	public boolean matches(Method method, Class targetClass, Object[] args) {
 		checkReadyToMatch();
 		ShadowMatch shadowMatch = getShadowMatch(AopUtils.getMostSpecificMethod(method, targetClass), method);
 		ShadowMatch originalShadowMatch = getShadowMatch(method, method);
@@ -318,64 +319,67 @@ public boolean matches(Method method, Class targetClass, Object[] args) {
 		catch (IllegalStateException ex) {
 			// No current invocation...
 			// TODO: Should we really proceed here?
-			logger.debug("Couldn't access current invocation - matching with limited context: " + ex);
-		}
-
-		JoinPointMatch joinPointMatch = shadowMatch.matchesJoinPoint(thisObject, targetObject, args);
-
-		/*
-		 * Do a final check to see if any this(TYPE) kind of residue match. For
-		 * this purpose, we use the original method's (proxy method's) shadow to
-		 * ensure that 'this' is correctly checked against. Without this check,
-		 * we get incorrect match on this(TYPE) where TYPE matches the target
-		 * type but not 'this' (as would be the case of JDK dynamic proxies).
-		 * 

See SPR-2979 for the original bug. - */ - if (pmi != null) { // there is a current invocation - RuntimeTestWalker originalMethodResidueTest = getRuntimeTestWalker(originalShadowMatch); - if (!originalMethodResidueTest.testThisInstanceOfResidue(thisObject.getClass())) { - return false; + if (logger.isDebugEnabled()) { + logger.debug("Could not access current invocation - matching with limited context: " + ex); } } - if (joinPointMatch.matches() && pmi != null) { - bindParameters(pmi, joinPointMatch); + + try { + JoinPointMatch joinPointMatch = shadowMatch.matchesJoinPoint(thisObject, targetObject, args); + + /* + * Do a final check to see if any this(TYPE) kind of residue match. For + * this purpose, we use the original method's (proxy method's) shadow to + * ensure that 'this' is correctly checked against. Without this check, + * we get incorrect match on this(TYPE) where TYPE matches the target + * type but not 'this' (as would be the case of JDK dynamic proxies). + *

See SPR-2979 for the original bug. + */ + if (pmi != null) { // there is a current invocation + RuntimeTestWalker originalMethodResidueTest = getRuntimeTestWalker(originalShadowMatch); + if (!originalMethodResidueTest.testThisInstanceOfResidue(thisObject.getClass())) { + return false; + } + if (joinPointMatch.matches()) { + bindParameters(pmi, joinPointMatch); + } + } + + return joinPointMatch.matches(); + } + catch (Throwable ex) { + if (logger.isDebugEnabled()) { + logger.debug("Failed to evaluate join point for arguments " + Arrays.asList(args) + + " - falling back to non-match", ex); + } + return false; } - return joinPointMatch.matches(); } - protected String getCurrentProxiedBeanName() { return ProxyCreationContext.getCurrentProxiedBeanName(); } /** - * Get a new pointcut expression based on a target class's loader, rather - * than the default. + * Get a new pointcut expression based on a target class's loader rather than the default. */ - private PointcutExpression getFallbackPointcutExpression( - Class targetClass) { - ClassLoader classLoader = targetClass.getClassLoader(); - return classLoader == null ? this.pointcutExpression : buildPointcutExpression(classLoader); - } - - /** - * A match test returned maybe - if there are any subtype sensitive variables - * involved in the test (this, target, at_this, at_target, at_annotation) then - * we say this is not a match as in Spring there will never be a different - * runtime subtype. - */ - private boolean matchesIgnoringSubtypes(ShadowMatch shadowMatch) { - return !(getRuntimeTestWalker(shadowMatch).testsSubtypeSensitiveVars()); - } - - private boolean matchesTarget(ShadowMatch shadowMatch, Class targetClass) { - return getRuntimeTestWalker(shadowMatch).testTargetInstanceOfResidue(targetClass); + private PointcutExpression getFallbackPointcutExpression(Class targetClass) { + try { + ClassLoader classLoader = targetClass.getClassLoader(); + if (classLoader != null && classLoader != this.pointcutClassLoader) { + return buildPointcutExpression(classLoader); + } + } + catch (Throwable ex) { + logger.debug("Failed to create fallback PointcutExpression", ex); + } + return null; } private RuntimeTestWalker getRuntimeTestWalker(ShadowMatch shadowMatch) { if (shadowMatch instanceof DefensiveShadowMatch) { - return new RuntimeTestWalker(((DefensiveShadowMatch)shadowMatch).primary); + return new RuntimeTestWalker(((DefensiveShadowMatch) shadowMatch).primary); } return new RuntimeTestWalker(shadowMatch); } @@ -396,44 +400,51 @@ private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) { if (shadowMatch == null) { synchronized (this.shadowMatchCache) { // Not found - now check again with full lock... + PointcutExpression fallbackExpression = null; Method methodToMatch = targetMethod; - PointcutExpression fallbackPointcutExpression = null; - shadowMatch = this.shadowMatchCache.get(methodToMatch); + shadowMatch = this.shadowMatchCache.get(targetMethod); if (shadowMatch == null) { try { - shadowMatch = this.pointcutExpression.matchesMethodExecution(targetMethod); + shadowMatch = this.pointcutExpression.matchesMethodExecution(methodToMatch); } - catch (ReflectionWorld.ReflectionWorldException ex) { + catch (ReflectionWorldException ex) { // Failed to introspect target method, probably because it has been loaded - // in a special ClassLoader. Let's try the original method instead... + // in a special ClassLoader. Let's try the declaring ClassLoader instead... try { - fallbackPointcutExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass()); - shadowMatch = fallbackPointcutExpression.matchesMethodExecution(methodToMatch); - } catch (ReflectionWorld.ReflectionWorldException e) { - if (targetMethod == originalMethod) { - shadowMatch = new ShadowMatchImpl(org.aspectj.util.FuzzyBoolean.NO, null, null, null); + fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass()); + if (fallbackExpression != null) { + shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch); } - else { - try { - shadowMatch = this.pointcutExpression.matchesMethodExecution(originalMethod); - } - catch (ReflectionWorld.ReflectionWorldException ex2) { - // Could neither introspect the target class nor the proxy class -> - // let's simply consider this method as non-matching. - methodToMatch = originalMethod; - fallbackPointcutExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass()); - try { - shadowMatch = fallbackPointcutExpression.matchesMethodExecution(methodToMatch); - } catch (ReflectionWorld.ReflectionWorldException e2) { - shadowMatch = new ShadowMatchImpl(org.aspectj.util.FuzzyBoolean.NO, null, null, null); - } + } + 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; + } } } - if (shadowMatch.maybeMatches() && fallbackPointcutExpression!=null) { + if (shadowMatch == null) { + shadowMatch = new ShadowMatchImpl(org.aspectj.util.FuzzyBoolean.NO, null, null, null); + } + else if (shadowMatch.maybeMatches() && fallbackExpression != null) { shadowMatch = new DefensiveShadowMatch(shadowMatch, - fallbackPointcutExpression.matchesMethodExecution(methodToMatch)); + fallbackExpression.matchesMethodExecution(methodToMatch)); } this.shadowMatchCache.put(targetMethod, shadowMatch); } @@ -551,7 +562,7 @@ public boolean mayNeedDynamicTest() { return false; } - private FuzzyBoolean contextMatch(Class targetType) { + private FuzzyBoolean contextMatch(Class targetType) { String advisedBeanName = getCurrentProxiedBeanName(); if (advisedBeanName == null) { // no proxy creation in progress // abstain; can't return YES, since that will make pointcut with negation fail @@ -601,9 +612,11 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound this.shadowMatchCache = new ConcurrentHashMap(32); } + private static class DefensiveShadowMatch implements ShadowMatch { private final ShadowMatch primary; + private final ShadowMatch other; public DefensiveShadowMatch(ShadowMatch primary, ShadowMatch other) { @@ -612,31 +625,30 @@ public DefensiveShadowMatch(ShadowMatch primary, ShadowMatch other) { } public boolean alwaysMatches() { - return primary.alwaysMatches(); + return this.primary.alwaysMatches(); } public boolean maybeMatches() { - return primary.maybeMatches(); + return this.primary.maybeMatches(); } public boolean neverMatches() { - return primary.neverMatches(); + return this.primary.neverMatches(); } - public JoinPointMatch matchesJoinPoint(Object thisObject, - Object targetObject, Object[] args) { + public JoinPointMatch matchesJoinPoint(Object thisObject, Object targetObject, Object[] args) { try { - return primary.matchesJoinPoint(thisObject, targetObject, args); - } catch (ReflectionWorldException e) { - return other.matchesJoinPoint(thisObject, targetObject, args); + return this.primary.matchesJoinPoint(thisObject, targetObject, args); + } + catch (ReflectionWorldException ex) { + return this.other.matchesJoinPoint(thisObject, targetObject, args); } } public void setMatchingContext(MatchingContext aMatchContext) { - primary.setMatchingContext(aMatchContext); - other.setMatchingContext(aMatchContext); + this.primary.setMatchingContext(aMatchContext); + this.other.setMatchingContext(aMatchContext); } - } } 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 70b6575076..09f2017b39 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-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,24 +39,24 @@ public void setExpression(String expression) { this.pointcut.setExpression(expression); } - public void setLocation(String location) { - this.pointcut.setLocation(location); - } - - public void setParameterTypes(Class[] types) { - this.pointcut.setParameterTypes(types); + public String getExpression() { + return this.pointcut.getExpression(); } - public void setParameterNames(String[] names) { - this.pointcut.setParameterNames(names); + public void setLocation(String location) { + this.pointcut.setLocation(location); } public String getLocation() { return this.pointcut.getLocation(); } - public String getExpression() { - return this.pointcut.getExpression(); + public void setParameterTypes(Class[] types) { + this.pointcut.setParameterTypes(types); + } + + public void setParameterNames(String... names) { + this.pointcut.setParameterNames(names); } } diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJWeaverMessageHandler.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJWeaverMessageHandler.java index 678c87baeb..018c2dc1c7 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJWeaverMessageHandler.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJWeaverMessageHandler.java @@ -45,43 +45,41 @@ public class AspectJWeaverMessageHandler implements IMessageHandler { private static final String AJ_ID = "[AspectJ] "; - private static final Log LOGGER = LogFactory.getLog("AspectJ Weaver"); + private static final Log logger = LogFactory.getLog("AspectJ Weaver"); public boolean handleMessage(IMessage message) throws AbortException { Kind messageKind = message.getKind(); - if (messageKind == IMessage.DEBUG) { - if (LOGGER.isDebugEnabled() || LOGGER.isTraceEnabled()) { - LOGGER.debug(makeMessageFor(message)); + if (logger.isDebugEnabled()) { + logger.debug(makeMessageFor(message)); return true; } } - else if ((messageKind == IMessage.INFO) || (messageKind == IMessage.WEAVEINFO)) { - if (LOGGER.isInfoEnabled()) { - LOGGER.info(makeMessageFor(message)); + else if (messageKind == IMessage.INFO || messageKind == IMessage.WEAVEINFO) { + if (logger.isInfoEnabled()) { + logger.info(makeMessageFor(message)); return true; } } else if (messageKind == IMessage.WARNING) { - if (LOGGER.isWarnEnabled()) { - LOGGER.warn(makeMessageFor(message)); + if (logger.isWarnEnabled()) { + logger.warn(makeMessageFor(message)); return true; } } else if (messageKind == IMessage.ERROR) { - if (LOGGER.isErrorEnabled()) { - LOGGER.error(makeMessageFor(message)); + if (logger.isErrorEnabled()) { + logger.error(makeMessageFor(message)); return true; } } else if (messageKind == IMessage.ABORT) { - if (LOGGER.isFatalEnabled()) { - LOGGER.fatal(makeMessageFor(message)); + if (logger.isFatalEnabled()) { + logger.fatal(makeMessageFor(message)); return true; } } - return false; } 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 27618c9b46..b437755ec1 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 @@ -34,7 +34,7 @@ */ public class DeclareParentsAdvisor implements IntroductionAdvisor { - private final Class introducedInterface; + private final Class introducedInterface; private final ClassFilter typePatternClassFilter; @@ -47,7 +47,7 @@ public class DeclareParentsAdvisor implements IntroductionAdvisor { * @param typePattern type pattern the introduction is restricted to * @param defaultImpl the default implementation class */ - public DeclareParentsAdvisor(Class interfaceType, String typePattern, Class defaultImpl) { + public DeclareParentsAdvisor(Class interfaceType, String typePattern, Class defaultImpl) { this(interfaceType, typePattern, defaultImpl, new DelegatePerTargetObjectIntroductionInterceptor(defaultImpl, interfaceType)); } @@ -58,7 +58,7 @@ public DeclareParentsAdvisor(Class interfaceType, String typePattern, Class defa * @param typePattern type pattern the introduction is restricted to * @param delegateRef the delegate implementation object */ - public DeclareParentsAdvisor(Class interfaceType, String typePattern, Object delegateRef) { + public DeclareParentsAdvisor(Class interfaceType, String typePattern, Object delegateRef) { this(interfaceType, typePattern, delegateRef.getClass(), new DelegatingIntroductionInterceptor(delegateRef)); } @@ -71,13 +71,13 @@ public DeclareParentsAdvisor(Class interfaceType, String typePattern, Object del * @param implementationClass implementation class * @param advice delegation advice */ - private DeclareParentsAdvisor(Class interfaceType, String typePattern, Class implementationClass, Advice advice) { + private DeclareParentsAdvisor(Class interfaceType, String typePattern, Class implementationClass, Advice advice) { this.introducedInterface = interfaceType; ClassFilter typePatternFilter = new TypePatternClassFilter(typePattern); // Excludes methods implemented. ClassFilter exclusion = new ClassFilter() { - public boolean matches(Class clazz) { + public boolean matches(Class clazz) { return !(introducedInterface.isAssignableFrom(clazz)); } }; @@ -103,8 +103,8 @@ public Advice getAdvice() { return this.advice; } - public Class[] getInterfaces() { - return new Class[] {this.introducedInterface}; + public Class[] getInterfaces() { + return new Class[] {this.introducedInterface}; } } diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/RuntimeTestWalker.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/RuntimeTestWalker.java index fa470759cd..0b20e8dc6e 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/RuntimeTestWalker.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/RuntimeTestWalker.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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 java.lang.reflect.Field; +import org.aspectj.weaver.ReferenceType; +import org.aspectj.weaver.ReferenceTypeDelegate; import org.aspectj.weaver.ResolvedType; import org.aspectj.weaver.ast.And; import org.aspectj.weaver.ast.Call; @@ -30,6 +32,7 @@ import org.aspectj.weaver.ast.Or; import org.aspectj.weaver.ast.Test; import org.aspectj.weaver.internal.tools.MatchingContextBasedTest; +import org.aspectj.weaver.reflect.ReflectionBasedReferenceTypeDelegate; import org.aspectj.weaver.reflect.ReflectionVar; import org.aspectj.weaver.reflect.ShadowMatchImpl; import org.aspectj.weaver.tools.ShadowMatch; @@ -55,25 +58,36 @@ */ class RuntimeTestWalker { - private final Test runtimeTest; + private static final Field residualTestField; + private static final Field varTypeField; - public RuntimeTestWalker(ShadowMatch shadowMatch) { - ShadowMatchImpl shadowMatchImplementation = (ShadowMatchImpl) shadowMatch; + private static final Field myClassField; + + + static { try { - Field testField = shadowMatchImplementation.getClass().getDeclaredField("residualTest"); - ReflectionUtils.makeAccessible(testField); - this.runtimeTest = (Test) testField.get(shadowMatch); + residualTestField = ShadowMatchImpl.class.getDeclaredField("residualTest"); + varTypeField = ReflectionVar.class.getDeclaredField("varType"); + myClassField = ReflectionBasedReferenceTypeDelegate.class.getDeclaredField("myClass"); } - catch (NoSuchFieldException noSuchFieldEx) { + catch (NoSuchFieldException ex) { throw new IllegalStateException("The version of aspectjtools.jar / aspectjweaver.jar " + - "on the classpath is incompatible with this version of Spring: Expected field " + - "'runtimeTest' is not present on ShadowMatchImpl class."); + "on the classpath is incompatible with this version of Spring: " + ex); + } + } + + + private final Test runtimeTest; + + + public RuntimeTestWalker(ShadowMatch shadowMatch) { + try { + ReflectionUtils.makeAccessible(residualTestField); + this.runtimeTest = (Test) residualTestField.get(shadowMatch); } - catch (IllegalAccessException illegalAccessEx) { - // Famous last words... but I don't see how this can happen given the - // makeAccessible call above - throw new IllegalStateException("Unable to access ShadowMatchImpl.residualTest field"); + catch (IllegalAccessException ex) { + throw new IllegalStateException(ex); } } @@ -87,12 +101,12 @@ public boolean testsSubtypeSensitiveVars() { new SubtypeSensitiveVarTypeTestVisitor().testsSubtypeSensitiveVars(this.runtimeTest)); } - public boolean testThisInstanceOfResidue(Class thisClass) { + public boolean testThisInstanceOfResidue(Class thisClass) { return (this.runtimeTest != null && new ThisInstanceOfResidueTestVisitor(thisClass).thisInstanceOfMatches(this.runtimeTest)); } - public boolean testTargetInstanceOfResidue(Class targetClass) { + public boolean testTargetInstanceOfResidue(Class targetClass) { return (this.runtimeTest != null && new TargetInstanceOfResidueTestVisitor(targetClass).targetInstanceOfMatches(this.runtimeTest)); } @@ -140,19 +154,11 @@ public void visit(MatchingContextBasedTest matchingContextTest) { protected int getVarType(ReflectionVar v) { try { - Field varTypeField = ReflectionVar.class.getDeclaredField("varType"); ReflectionUtils.makeAccessible(varTypeField); return (Integer) varTypeField.get(v); } - catch (NoSuchFieldException noSuchFieldEx) { - throw new IllegalStateException("the version of aspectjtools.jar / aspectjweaver.jar " + - "on the classpath is incompatible with this version of Spring:- expected field " + - "'varType' is not present on ReflectionVar class"); - } - catch (IllegalAccessException illegalAccessEx) { - // Famous last words... but I don't see how this can happen given the - // makeAccessible call above - throw new IllegalStateException("Unable to access ReflectionVar.varType field"); + catch (IllegalAccessException ex) { + throw new IllegalStateException(ex); } } } @@ -160,11 +166,13 @@ protected int getVarType(ReflectionVar v) { private static abstract class InstanceOfResidueTestVisitor extends TestVisitorAdapter { - private Class matchClass; + private final Class matchClass; + private boolean matches; - private int matchVarType; - public InstanceOfResidueTestVisitor(Class matchClass, boolean defaultMatches, int matchVarType) { + private final int matchVarType; + + public InstanceOfResidueTestVisitor(Class matchClass, boolean defaultMatches, int matchVarType) { this.matchClass = matchClass; this.matches = defaultMatches; this.matchVarType = matchVarType; @@ -172,19 +180,34 @@ public InstanceOfResidueTestVisitor(Class matchClass, boolean defaultMatches, in public boolean instanceOfMatches(Test test) { test.accept(this); - return matches; + return this.matches; } @Override public void visit(Instanceof i) { - ResolvedType type = (ResolvedType) i.getType(); int varType = getVarType((ReflectionVar) i.getVar()); if (varType != this.matchVarType) { return; } + Class typeClass = null; + ResolvedType type = (ResolvedType) i.getType(); + if (type instanceof ReferenceType) { + ReferenceTypeDelegate delegate = ((ReferenceType) type).getDelegate(); + if (delegate instanceof ReflectionBasedReferenceTypeDelegate) { + try { + ReflectionUtils.makeAccessible(myClassField); + typeClass = (Class) myClassField.get(delegate); + } + catch (IllegalAccessException ex) { + throw new IllegalStateException(ex); + } + } + } try { - Class typeClass = ClassUtils.forName(type.getName(), this.matchClass.getClassLoader()); - // Don't use ReflectionType.isAssignableFrom() as it won't be aware of (Spring) mixins + // Don't use ResolvedType.isAssignableFrom() as it won't be aware of (Spring) mixins + if (typeClass == null) { + typeClass = ClassUtils.forName(type.getName(), this.matchClass.getClassLoader()); + } this.matches = typeClass.isAssignableFrom(this.matchClass); } catch (ClassNotFoundException ex) { @@ -199,7 +222,7 @@ public void visit(Instanceof i) { */ private static class TargetInstanceOfResidueTestVisitor extends InstanceOfResidueTestVisitor { - public TargetInstanceOfResidueTestVisitor(Class targetClass) { + public TargetInstanceOfResidueTestVisitor(Class targetClass) { super(targetClass, false, TARGET_VAR); } @@ -214,7 +237,7 @@ public boolean targetInstanceOfMatches(Test test) { */ private static class ThisInstanceOfResidueTestVisitor extends InstanceOfResidueTestVisitor { - public ThisInstanceOfResidueTestVisitor(Class thisClass) { + public ThisInstanceOfResidueTestVisitor(Class thisClass) { super(thisClass, true, THIS_VAR); } @@ -228,8 +251,11 @@ public boolean thisInstanceOfMatches(Test test) { private static class SubtypeSensitiveVarTypeTestVisitor extends TestVisitorAdapter { private final Object thisObj = new Object(); + private final Object targetObj = new Object(); + private final Object[] argsObjs = new Object[0]; + private boolean testsSubtypeSensitiveVars = false; public boolean testsSubtypeSensitiveVars(Test aTest) { @@ -240,8 +266,8 @@ public boolean testsSubtypeSensitiveVars(Test aTest) { @Override public void visit(Instanceof i) { ReflectionVar v = (ReflectionVar) i.getVar(); - Object varUnderTest = v.getBindingAtJoinPoint(thisObj,targetObj,argsObjs); - if ((varUnderTest == thisObj) || (varUnderTest == targetObj)) { + Object varUnderTest = v.getBindingAtJoinPoint(this.thisObj, this.targetObj, this.argsObjs); + if (varUnderTest == this.thisObj || varUnderTest == this.targetObj) { this.testsSubtypeSensitiveVars = true; } } @@ -251,7 +277,7 @@ public void visit(HasAnnotation hasAnn) { // If you thought things were bad before, now we sink to new levels of horror... ReflectionVar v = (ReflectionVar) hasAnn.getVar(); int varType = getVarType(v); - if ((varType == AT_THIS_VAR) || (varType == AT_TARGET_VAR) || (varType == AT_ANNOTATION_VAR)) { + if (varType == AT_THIS_VAR || varType == AT_TARGET_VAR || varType == AT_ANNOTATION_VAR) { this.testsSubtypeSensitiveVars = true; } } 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 9eff81682b..dce613ef5d 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-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,7 +67,7 @@ public void setIncludePatterns(List patterns) { } public void setAspectJAdvisorFactory(AspectJAdvisorFactory aspectJAdvisorFactory) { - Assert.notNull(this.aspectJAdvisorFactory, "AspectJAdvisorFactory must not be null"); + Assert.notNull(aspectJAdvisorFactory, "AspectJAdvisorFactory must not be null"); this.aspectJAdvisorFactory = aspectJAdvisorFactory; } @@ -89,7 +89,7 @@ protected List findCandidateAdvisors() { } @Override - protected boolean isInfrastructureClass(Class beanClass) { + protected boolean isInfrastructureClass(Class beanClass) { // Previously we setProxyTargetClass(true) in the constructor, but that has too // broad an impact. Instead we now override isInfrastructureClass to avoid proxying // aspects. I'm not entirely happy with that as there is no good reason not 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 81a6c2a3ad..02de8995ac 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 @@ -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. @@ -18,8 +18,11 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.support.AbstractBeanFactory; import org.springframework.core.Ordered; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.Order; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** @@ -65,7 +68,9 @@ public BeanFactoryAspectInstanceFactory(BeanFactory beanFactory, String name) { * @param name the name of the bean * @param type the type that should be introspected by AspectJ */ - public BeanFactoryAspectInstanceFactory(BeanFactory beanFactory, String name, Class type) { + public BeanFactoryAspectInstanceFactory(BeanFactory beanFactory, String name, Class type) { + Assert.notNull(beanFactory, "BeanFactory must not be null"); + Assert.notNull(name, "Bean name must not be null"); this.beanFactory = beanFactory; this.name = name; this.aspectMetadata = new AspectMetadata(type, name); @@ -77,18 +82,31 @@ public Object getAspectInstance() { } public ClassLoader getAspectClassLoader() { - if (this.beanFactory instanceof ConfigurableBeanFactory) { - return ((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader(); - } - else { - return ClassUtils.getDefaultClassLoader(); - } + return (this.beanFactory instanceof ConfigurableBeanFactory ? + ((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader() : + ClassUtils.getDefaultClassLoader()); } public AspectMetadata getAspectMetadata() { return this.aspectMetadata; } + public Object getAspectCreationMutex() { + if (this.beanFactory != null) { + if (this.beanFactory.isSingleton(this.name)) { + // Rely on singleton semantics provided by the factory -> no local lock. + return null; + } + else if (this.beanFactory instanceof AbstractBeanFactory) { + // 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 ((AbstractBeanFactory) this.beanFactory).getSingletonMutex(); + } + } + return this; + } + /** * Determine the order for this factory's target aspect, either * an instance-specific order expressed through implementing the @@ -105,7 +123,7 @@ public int getOrder() { if (Ordered.class.isAssignableFrom(type) && this.beanFactory.isSingleton(this.name)) { return ((Ordered) this.beanFactory.getBean(this.name)).getOrder(); } - Order order = type.getAnnotation(Order.class); + Order order = AnnotationUtils.findAnnotation(type, Order.class); if (order != null) { return order.value(); } 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 39d28fff7e..df5a628b5e 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 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. @@ -42,11 +42,20 @@ public LazySingletonAspectInstanceFactoryDecorator(MetadataAwareAspectInstanceFa } - public synchronized Object getAspectInstance() { + public Object getAspectInstance() { if (this.materialized == null) { - synchronized (this) { - if (this.materialized == null) { - this.materialized = this.maaif.getAspectInstance(); + Object mutex = this; + if (this.maaif instanceof BeanFactoryAspectInstanceFactory) { + mutex = ((BeanFactoryAspectInstanceFactory) 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/SimpleMetadataAwareAspectInstanceFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/SimpleMetadataAwareAspectInstanceFactory.java index bb623b372b..0117030dc2 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/SimpleMetadataAwareAspectInstanceFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/SimpleMetadataAwareAspectInstanceFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import org.springframework.aop.aspectj.SimpleAspectInstanceFactory; import org.springframework.core.Ordered; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.Order; /** @@ -58,7 +59,7 @@ public final AspectMetadata getAspectMetadata() { */ @Override protected int getOrderForAspectClass(Class aspectClass) { - Order order = aspectClass.getAnnotation(Order.class); + Order order = AnnotationUtils.findAnnotation(aspectClass, Order.class); if (order != null) { return order.value(); } diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/SingletonMetadataAwareAspectInstanceFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/SingletonMetadataAwareAspectInstanceFactory.java index ef9014a144..6c079dbfa6 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/SingletonMetadataAwareAspectInstanceFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/SingletonMetadataAwareAspectInstanceFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import org.springframework.aop.aspectj.SingletonAspectInstanceFactory; import org.springframework.core.Ordered; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.Order; /** @@ -59,7 +60,7 @@ public final AspectMetadata getAspectMetadata() { */ @Override protected int getOrderForAspectClass(Class aspectClass) { - Order order = aspectClass.getAnnotation(Order.class); + Order order = AnnotationUtils.findAnnotation(aspectClass, Order.class); if (order != null) { return order.value(); } 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 01b0fe856c..c15baa504d 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-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,7 +49,7 @@ public abstract class AbstractAdvisingBeanPostProcessor extends ProxyConfig */ private int order = Ordered.LOWEST_PRECEDENCE; - private final Map eligibleBeans = new ConcurrentHashMap(64); + private final Map, Boolean> eligibleBeans = new ConcurrentHashMap, Boolean>(64); /** @@ -87,9 +87,11 @@ public Object postProcessAfterInitialization(Object bean, String beanName) { // Ignore AOP infrastructure such as scoped proxies. return bean; } - if (isEligible(bean, beanName)) { - if (bean instanceof Advised) { - Advised advised = (Advised) bean; + + if (bean instanceof Advised) { + Advised advised = (Advised) bean; + if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) { + // Add our local Advisor to the existing proxy's Advisor chain... if (this.beforeExistingAdvisors) { advised.addAdvisor(0, this.advisor); } @@ -98,32 +100,47 @@ public Object postProcessAfterInitialization(Object bean, String beanName) { } return bean; } - else { - ProxyFactory proxyFactory = new ProxyFactory(bean); - // Copy our properties (proxyTargetClass etc) inherited from ProxyConfig. - proxyFactory.copyFrom(this); - proxyFactory.addAdvisor(this.advisor); - return proxyFactory.getProxy(this.beanClassLoader); - } } - else { - // No async proxy needed. - return bean; + + if (isEligible(bean, beanName)) { + ProxyFactory proxyFactory = new ProxyFactory(bean); + // Copy our properties (proxyTargetClass etc) inherited from ProxyConfig. + proxyFactory.copyFrom(this); + proxyFactory.addAdvisor(this.advisor); + return proxyFactory.getProxy(this.beanClassLoader); } + + // No async proxy needed. + return bean; } /** * Check whether the given bean is eligible for advising with this * post-processor's {@link Advisor}. - *

Implements caching of {@code canApply} results per bean target class. + *

Delegates to {@link #isEligible(Class)} for target class checking. * Can be overridden e.g. to specifically exclude certain beans by name. + *

Note: Only called for regular bean instances but not for existing + * proxy instances which implement {@link Advised} and allow for adding + * the local {@link Advisor} to the existing proxy's {@link Advisor} chain. + * For the latter, {@link #isEligible(Class)} is being called directly, + * with the actual target class behind the existing proxy (as determined + * by {@link AopUtils#getTargetClass(Object)}). * @param bean the bean instance * @param beanName the name of the bean - * @see AopUtils#getTargetClass(Object) - * @see AopUtils#canApply(Advisor, Class) + * @see #isEligible(Class) */ protected boolean isEligible(Object bean, String beanName) { - Class targetClass = AopUtils.getTargetClass(bean); + return isEligible(bean.getClass()); + } + + /** + * Check whether the given class is eligible for advising with this + * post-processor's {@link Advisor}. + *

Implements caching of {@code canApply} results per bean target class. + * @param targetClass the class to check against + * @see AopUtils#canApply(Advisor, Class) + */ + protected boolean isEligible(Class targetClass) { Boolean eligible = this.eligibleBeans.get(targetClass); if (eligible != null) { return eligible; diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/Advised.java b/spring-aop/src/main/java/org/springframework/aop/framework/Advised.java index 3c822cf2da..232e2ebc39 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/Advised.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/Advised.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,13 +52,13 @@ public interface Advised extends TargetClassAware { * Return the interfaces proxied by the AOP proxy. Will not * include the target class, which may also be proxied. */ - Class[] getProxiedInterfaces(); + Class[] getProxiedInterfaces(); /** * Determine whether the given interface is proxied. * @param intf the interface to check */ - boolean isInterfaceProxied(Class intf); + boolean isInterfaceProxied(Class intf); /** 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 e5f89177b6..2d6f4b7eb0 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-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -158,7 +158,7 @@ public TargetSource getTargetSource() { * @see #setTargetSource * @see #setTarget */ - public void setTargetClass(Class targetClass) { + public void setTargetClass(Class targetClass) { this.targetSource = EmptyTargetSource.forClass(targetClass); } @@ -194,7 +194,7 @@ public AdvisorChainFactory getAdvisorChainFactory() { /** * Set the interfaces to be proxied. */ - public void setInterfaces(Class[] interfaces) { + public void setInterfaces(Class... interfaces) { Assert.notNull(interfaces, "Interfaces must not be null"); this.interfaces.clear(); for (Class ifc : interfaces) { @@ -206,7 +206,7 @@ public void setInterfaces(Class[] interfaces) { * Add a new proxied interface. * @param intf the additional interface to proxy */ - public void addInterface(Class intf) { + public void addInterface(Class intf) { Assert.notNull(intf, "Interface must not be null"); if (!intf.isInterface()) { throw new IllegalArgumentException("[" + intf.getName() + "] is not an interface"); @@ -224,15 +224,15 @@ public void addInterface(Class intf) { * @return {@code true} if the interface was removed; {@code false} * if the interface was not found and hence could not be removed */ - public boolean removeInterface(Class intf) { + public boolean removeInterface(Class intf) { return this.interfaces.remove(intf); } - public Class[] getProxiedInterfaces() { + public Class[] getProxiedInterfaces() { return this.interfaces.toArray(new Class[this.interfaces.size()]); } - public boolean isInterfaceProxied(Class intf) { + public boolean isInterfaceProxied(Class intf) { for (Class proxyIntf : this.interfaces) { if (intf.isAssignableFrom(proxyIntf)) { return true; 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 5801458ff0..8c1e1246b1 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-2012 the original author or authors. + * Copyright 2002-2015 the original author 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,27 +25,28 @@ import java.util.Map; import java.util.WeakHashMap; -import org.springframework.cglib.core.CodeGenerationException; -import org.springframework.cglib.proxy.Callback; -import org.springframework.cglib.proxy.CallbackFilter; -import org.springframework.cglib.proxy.Dispatcher; -import org.springframework.cglib.proxy.Enhancer; -import org.springframework.cglib.proxy.Factory; -import org.springframework.cglib.proxy.MethodInterceptor; -import org.springframework.cglib.proxy.MethodProxy; -import org.springframework.cglib.proxy.NoOp; -import org.springframework.cglib.transform.impl.UndeclaredThrowableStrategy; - import org.aopalliance.aop.Advice; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.aop.Advisor; import org.springframework.aop.AopInvocationException; import org.springframework.aop.PointcutAdvisor; import org.springframework.aop.RawTargetAccess; import org.springframework.aop.TargetSource; import org.springframework.aop.support.AopUtils; +import org.springframework.cglib.core.CodeGenerationException; +import org.springframework.cglib.core.SpringNamingPolicy; +import org.springframework.cglib.proxy.Callback; +import org.springframework.cglib.proxy.CallbackFilter; +import org.springframework.cglib.proxy.Dispatcher; +import org.springframework.cglib.proxy.Enhancer; +import org.springframework.cglib.proxy.Factory; +import org.springframework.cglib.proxy.MethodInterceptor; +import org.springframework.cglib.proxy.MethodProxy; +import org.springframework.cglib.proxy.NoOp; +import org.springframework.cglib.transform.impl.MemorySafeUndeclaredThrowableStrategy; import org.springframework.core.SmartClassLoader; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -92,7 +93,7 @@ final class CglibAopProxy implements AopProxy, Serializable { /** Logger available to subclasses; static to optimize serialization */ - protected final static Log logger = LogFactory.getLog(CglibAopProxy.class); + protected static final Log logger = LogFactory.getLog(CglibAopProxy.class); /** Keeps track of the Classes that we have validated for final methods */ private static final Map, Boolean> validatedClasses = new WeakHashMap, Boolean>(); @@ -181,20 +182,20 @@ public Object getProxy(ClassLoader classLoader) { } } enhancer.setSuperclass(proxySuperClass); - enhancer.setStrategy(new UndeclaredThrowableStrategy(UndeclaredThrowableException.class)); enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised)); + enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); + enhancer.setStrategy(new MemorySafeUndeclaredThrowableStrategy(UndeclaredThrowableException.class)); enhancer.setInterceptDuringConstruction(false); Callback[] callbacks = getCallbacks(rootClass); - enhancer.setCallbacks(callbacks); - enhancer.setCallbackFilter(new ProxyCallbackFilter( - this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset)); - - Class[] types = new Class[callbacks.length]; + Class[] types = new Class[callbacks.length]; for (int x = 0; x < types.length; x++) { types[x] = callbacks[x].getClass(); } + enhancer.setCallbackFilter(new ProxyCallbackFilter( + this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset)); enhancer.setCallbackTypes(types); + enhancer.setCallbacks(callbacks); // Generate the proxy class and create a proxy instance. Object proxy; @@ -253,11 +254,14 @@ private void validateClassIfNecessary(Class proxySuperClass) { * for each one found. */ private void doValidateClass(Class proxySuperClass) { - Method[] methods = proxySuperClass.getMethods(); - for (Method method : methods) { - if (!Object.class.equals(method.getDeclaringClass()) && Modifier.isFinal(method.getModifiers())) { - logger.warn("Unable to proxy method [" + method + "] because it is final: " + - "All calls to this method via a proxy will be routed directly to the proxy."); + if (logger.isWarnEnabled()) { + Method[] methods = proxySuperClass.getMethods(); + for (Method method : methods) { + if (!Object.class.equals(method.getDeclaringClass()) && !Modifier.isStatic(method.getModifiers()) && + Modifier.isFinal(method.getModifiers())) { + logger.warn("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."); + } } } } @@ -290,13 +294,13 @@ private Callback[] getCallbacks(Class rootClass) throws Exception { Callback targetDispatcher = isStatic ? new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp(); - Callback[] mainCallbacks = new Callback[]{ - aopInterceptor, // for normal advice - targetInterceptor, // invoke target without considering advice, if optimized - new SerializableNoOp(), // no override for methods mapped to this - targetDispatcher, this.advisedDispatcher, - new EqualsInterceptor(this.advised), - new HashCodeInterceptor(this.advised) + Callback[] mainCallbacks = new Callback[] { + aopInterceptor, // for normal advice + targetInterceptor, // invoke target without considering advice, if optimized + new SerializableNoOp(), // no override for methods mapped to this + targetDispatcher, this.advisedDispatcher, + new EqualsInterceptor(this.advised), + new HashCodeInterceptor(this.advised) }; Callback[] callbacks; @@ -309,8 +313,7 @@ private Callback[] getCallbacks(Class rootClass) throws Exception { 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 optimisation 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( @@ -337,16 +340,15 @@ private Callback[] getCallbacks(Class rootClass) throws Exception { */ 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())) { - // Special case: it returned "this". - // Note that we can't help if the target sets a reference - // to itself in another returned object. + 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; } Class returnType = method.getReturnType(); if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { - throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method); + throw new AopInvocationException( + "Null return value from advice does not match primitive return type for: " + method); } return retVal; } @@ -592,7 +594,7 @@ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy */ private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable { - private AdvisedSupport advised; + private final AdvisedSupport advised; public DynamicAdvisedInterceptor(AdvisedSupport advised) { this.advised = advised; @@ -609,8 +611,8 @@ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } - // May be null Get as late as possible to minimize the time we - // "own" the target, in case it comes from a pool. + // May be null. Get as late as possible to minimize the time we + // "own" the target, in case it comes from a pool... target = getTarget(); if (target != null) { targetClass = target.getClass(); @@ -676,10 +678,11 @@ private static class CglibMethodInvocation extends ReflectiveMethodInvocation { private final MethodProxy methodProxy; - private boolean protectedMethod; + private final boolean protectedMethod; public CglibMethodInvocation(Object proxy, Object target, Method method, Object[] arguments, Class targetClass, List interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy) { + super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers); this.methodProxy = methodProxy; this.protectedMethod = Modifier.isProtected(method.getModifiers()); @@ -798,8 +801,7 @@ public int accept(Method method) { if (logger.isDebugEnabled()) { logger.debug("Method has advice and optimisations are enabled: " + method); } - // We know that we are optimising so we can use the - // FixedStaticChainInterceptors. + // We know that we are optimising so we can use the FixedStaticChainInterceptors. int index = this.fixedInterceptorMap.get(key); return (index + this.fixedInterceptorOffset); } @@ -815,8 +817,8 @@ public int accept(Method method) { // 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 cannot - // use a Dispatcher because the target can not then be released. + // correct one is already configured. If the target is not static, then + // cannot use a dispatcher because the target cannot be released. if (exposeProxy || !isStatic) { return INVOKE_TARGET; } @@ -848,7 +850,7 @@ else if (returnType.isPrimitive() || !returnType.isAssignableFrom(targetClass)) @Override public boolean equals(Object other) { - if (other == this) { + if (this == other) { return true; } if (!(other instanceof ProxyCallbackFilter)) { 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 7e10c4c228..75967053d4 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa */ /** We use a static Log to avoid serialization issues */ - private static Log logger = LogFactory.getLog(JdkDynamicAopProxy.class); + private static final Log logger = LogFactory.getLog(JdkDynamicAopProxy.class); /** Config used to configure this proxy */ private final AdvisedSupport advised; @@ -114,7 +114,7 @@ public Object getProxy(ClassLoader classLoader) { if (logger.isDebugEnabled()) { logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); } - Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised); + Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); } @@ -124,8 +124,8 @@ public Object getProxy(ClassLoader classLoader) { * on the supplied set of interfaces. * @param proxiedInterfaces the interfaces to introspect */ - private void findDefinedEqualsAndHashCodeMethods(Class[] proxiedInterfaces) { - for (Class proxiedInterface : proxiedInterfaces) { + private void findDefinedEqualsAndHashCodeMethods(Class[] proxiedInterfaces) { + for (Class proxiedInterface : proxiedInterfaces) { Method[] methods = proxiedInterface.getDeclaredMethods(); for (Method method : methods) { if (AopUtils.isEqualsMethod(method)) { @@ -153,7 +153,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource; - Class targetClass = null; + Class targetClass = null; Object target = null; try { @@ -212,8 +212,10 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl // is type-compatible. Note that we can't help if the target sets // a reference to itself in another returned object. retVal = proxy; - } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { - throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method); + } + else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { + throw new AopInvocationException( + "Null return value from advice does not match primitive return type for: " + method); } return retVal; } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java index 67e89eeefd..db1e19a359 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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 org.aopalliance.intercept.Interceptor; import org.springframework.aop.TargetSource; -import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** @@ -47,9 +46,8 @@ public ProxyFactory() { * @param target the target object to be proxied */ public ProxyFactory(Object target) { - Assert.notNull(target, "Target object must not be null"); - setInterfaces(ClassUtils.getAllInterfaces(target)); setTarget(target); + setInterfaces(ClassUtils.getAllInterfaces(target)); } /** @@ -57,7 +55,7 @@ public ProxyFactory(Object target) { *

No target, only interfaces. Must add interceptors. * @param proxyInterfaces the interfaces that the proxy should implement */ - public ProxyFactory(Class[] proxyInterfaces) { + public ProxyFactory(Class... proxyInterfaces) { setInterfaces(proxyInterfaces); } @@ -69,7 +67,7 @@ public ProxyFactory(Class[] proxyInterfaces) { * @param proxyInterface the interface that the proxy should implement * @param interceptor the interceptor that the proxy should invoke */ - public ProxyFactory(Class proxyInterface, Interceptor interceptor) { + public ProxyFactory(Class proxyInterface, Interceptor interceptor) { addInterface(proxyInterface); addAdvice(interceptor); } @@ -80,7 +78,7 @@ public ProxyFactory(Class proxyInterface, Interceptor interceptor) { * @param proxyInterface the interface that the proxy should implement * @param targetSource the TargetSource that the proxy should invoke */ - public ProxyFactory(Class proxyInterface, TargetSource targetSource) { + public ProxyFactory(Class proxyInterface, TargetSource targetSource) { addInterface(proxyInterface); setTargetSource(targetSource); } 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 a369f56c82..9781364df0 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-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -133,7 +133,7 @@ public class ProxyFactoryBean extends ProxyCreatorSupport * @see #setInterfaces * @see AbstractSingletonProxyFactoryBean#setProxyInterfaces */ - public void setProxyInterfaces(Class[] proxyInterfaces) throws ClassNotFoundException { + public void setProxyInterfaces(Class[] proxyInterfaces) throws ClassNotFoundException { setInterfaces(proxyInterfaces); } @@ -154,7 +154,7 @@ public void setProxyInterfaces(Class[] proxyInterfaces) throws ClassNotFoundExce * @see org.aopalliance.aop.Advice * @see org.springframework.aop.target.SingletonTargetSource */ - public void setInterceptorNames(String[] interceptorNames) { + public void setInterceptorNames(String... interceptorNames) { this.interceptorNames = interceptorNames; } @@ -263,7 +263,7 @@ public Class getObjectType() { return this.singletonInstance.getClass(); } } - Class[] ifcs = getProxiedInterfaces(); + Class[] ifcs = getProxiedInterfaces(); if (ifcs.length == 1) { return ifcs[0]; } @@ -292,7 +292,7 @@ public boolean isSingleton() { * @return the merged interface as Class * @see java.lang.reflect.Proxy#getProxyClass */ - protected Class createCompositeInterface(Class[] interfaces) { + protected Class createCompositeInterface(Class[] interfaces) { return ClassUtils.createCompositeInterface(interfaces, this.proxyClassLoader); } @@ -306,7 +306,7 @@ private synchronized Object getSingletonInstance() { this.targetSource = freshTargetSource(); if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) { // Rely on AOP infrastructure to tell us what interfaces to proxy. - Class targetClass = getTargetClass(); + Class targetClass = getTargetClass(); if (targetClass == null) { throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy"); } @@ -396,7 +396,7 @@ private void checkInterceptorNames() { * @return {@code true} if it's an Advisor or Advice */ private boolean isNamedBeanAnAdvisorOrAdvice(String beanName) { - Class namedBeanClass = this.beanFactory.getType(beanName); + Class namedBeanClass = this.beanFactory.getType(beanName); if (namedBeanClass != null) { return (Advisor.class.isAssignableFrom(namedBeanClass) || Advice.class.isAssignableFrom(namedBeanClass)); } 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 d66adc843c..1c272f7bca 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-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -198,7 +198,7 @@ public void setAdvisorAdapterRegistry(AdvisorAdapterRegistry advisorAdapterRegis * Ordering is significant: The TargetSource returned from the first matching * TargetSourceCreator (that is, the first that returns non-null) will be used. */ - public void setCustomTargetSourceCreators(TargetSourceCreator[] targetSourceCreators) { + public void setCustomTargetSourceCreators(TargetSourceCreator... targetSourceCreators) { this.customTargetSourceCreators = targetSourceCreators; } @@ -209,7 +209,7 @@ public void setCustomTargetSourceCreators(TargetSourceCreator[] targetSourceCrea * This is perfectly valid, if "specific" interceptors such as matching * Advisors are all we want. */ - public void setInterceptorNames(String[] interceptorNames) { + public void setInterceptorNames(String... interceptorNames) { this.interceptorNames = interceptorNames; } @@ -262,7 +262,9 @@ public Constructor[] determineCandidateConstructors(Class beanClass, Strin public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException { Object cacheKey = getCacheKey(bean.getClass(), beanName); - this.earlyProxyReferences.put(cacheKey, Boolean.TRUE); + if (!this.earlyProxyReferences.containsKey(cacheKey)) { + this.earlyProxyReferences.put(cacheKey, Boolean.TRUE); + } return wrapIfNecessary(bean, beanName, cacheKey); } 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 e417f92df0..c484520b89 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-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,24 +81,32 @@ public List findAdvisorBeans() { List advisors = new LinkedList(); for (String name : advisorNames) { - if (isEligibleBean(name) && !this.beanFactory.isCurrentlyInCreation(name)) { - try { - advisors.add(this.beanFactory.getBean(name, Advisor.class)); + if (isEligibleBean(name)) { + if (this.beanFactory.isCurrentlyInCreation(name)) { + if (logger.isDebugEnabled()) { + logger.debug("Skipping currently created advisor '" + name + "'"); + } } - catch (BeanCreationException ex) { - Throwable rootCause = ex.getMostSpecificCause(); - if (rootCause instanceof BeanCurrentlyInCreationException) { - BeanCreationException bce = (BeanCreationException) rootCause; - if (this.beanFactory.isCurrentlyInCreation(bce.getBeanName())) { - if (logger.isDebugEnabled()) { - logger.debug("Ignoring currently created advisor '" + name + "': " + ex.getMessage()); + else { + try { + advisors.add(this.beanFactory.getBean(name, Advisor.class)); + } + catch (BeanCreationException ex) { + Throwable rootCause = ex.getMostSpecificCause(); + if (rootCause instanceof BeanCurrentlyInCreationException) { + BeanCreationException bce = (BeanCreationException) rootCause; + if (this.beanFactory.isCurrentlyInCreation(bce.getBeanName())) { + if (logger.isDebugEnabled()) { + logger.debug("Skipping advisor '" + name + + "' with dependency on currently created bean: " + ex.getMessage()); + } + // Ignore: indicates a reference back to the bean we're trying to advise. + // We want to find advisors other than the currently created bean itself. + continue; } - // Ignore: indicates a reference back to the bean we're trying to advise. - // We want to find advisors other than the currently created bean itself. - continue; } + throw ex; } - throw ex; } } } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanNameAutoProxyCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanNameAutoProxyCreator.java index e91f9db74a..5ae853dc36 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanNameAutoProxyCreator.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanNameAutoProxyCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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 class BeanNameAutoProxyCreator extends AbstractAutoProxyCreator { * @see org.springframework.beans.factory.FactoryBean * @see org.springframework.beans.factory.BeanFactory#FACTORY_BEAN_PREFIX */ - public void setBeanNames(String[] beanNames) { + public void setBeanNames(String... beanNames) { Assert.notEmpty(beanNames, "'beanNames' must not be empty"); this.beanNames = new ArrayList(beanNames.length); for (String mappedName : beanNames) { @@ -73,7 +73,7 @@ public void setBeanNames(String[] beanNames) { * Identify as bean to proxy if the bean name is in the configured list of names. */ @Override - protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, TargetSource targetSource) { + protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, TargetSource targetSource) { if (this.beanNames != null) { for (String mappedName : this.beanNames) { if (FactoryBean.class.isAssignableFrom(beanClass)) { diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/LazyInitTargetSourceCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/LazyInitTargetSourceCreator.java index a32f3a3f2c..55d091394c 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/LazyInitTargetSourceCreator.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/LazyInitTargetSourceCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2006 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ * TargetSourceCreator that enforces a LazyInitTargetSource for each bean * that is defined as "lazy-init". This will lead to a proxy created for * each of those beans, allowing to fetch a reference to such a bean - * without actually initialized the target bean instance. + * without actually initializing the target bean instance. * *

To be registered as custom TargetSourceCreator for an auto-proxy creator, * in combination with custom interceptors for specific beans or for the @@ -60,7 +60,7 @@ protected boolean isPrototypeBased() { @Override protected AbstractBeanFactoryBasedTargetSource createBeanFactoryBasedTargetSource( - Class beanClass, String beanName) { + Class beanClass, String beanName) { if (getBeanFactory() instanceof ConfigurableListableBeanFactory) { BeanDefinition definition = 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 7fbfbf5604..4b8389f8b6 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-2013 the original author or authors. + * Copyright 2002-2015 the original author 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,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; @@ -32,7 +31,7 @@ /** * Base class for asynchronous method execution aspects, such as - * {@link org.springframework.scheduling.annotation.AnnotationAsyncExecutionInterceptor} + * {@code org.springframework.scheduling.annotation.AnnotationAsyncExecutionInterceptor} * or {@code org.springframework.scheduling.aspectj.AnnotationAsyncExecutionAspect}. * *

Provides support for executor qualification on a method-by-method basis. @@ -80,14 +79,14 @@ public void setExecutor(Executor defaultExecutor) { /** * Set the {@link BeanFactory} to be used when looking up executors by qualifier. */ - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; } /** * Determine the specific executor to use when executing the given method. - * @return the executor to use (never {@code null}) + * @return the executor to use (or {@code null}, but just if no default executor has been set) */ protected AsyncTaskExecutor determineAsyncExecutor(Method method) { AsyncTaskExecutor executor = this.executors.get(method); @@ -101,8 +100,7 @@ protected AsyncTaskExecutor determineAsyncExecutor(Method method) { this.beanFactory, Executor.class, qualifier); } else if (executorToUse == null) { - throw new IllegalStateException("No executor qualifier specified and no default executor set on " + - getClass().getSimpleName() + " either"); + return null; } executor = (executorToUse instanceof AsyncTaskExecutor ? (AsyncTaskExecutor) executorToUse : new TaskExecutorAdapter(executorToUse)); diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java index daa23a3293..52ba3bb7eb 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author 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,11 +62,11 @@ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport /** * Create a new {@code AsyncExecutionInterceptor}. - * @param executor the {@link Executor} (typically a Spring {@link AsyncTaskExecutor} - * or {@link java.util.concurrent.ExecutorService}) to delegate to. + * @param defaultExecutor the {@link Executor} (typically a Spring {@link AsyncTaskExecutor} + * or {@link java.util.concurrent.ExecutorService}) to delegate to */ - public AsyncExecutionInterceptor(Executor executor) { - super(executor); + public AsyncExecutionInterceptor(Executor defaultExecutor) { + super(defaultExecutor); } @@ -82,7 +82,13 @@ public Object invoke(final MethodInvocation invocation) throws Throwable { Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass); specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); - Future result = determineAsyncExecutor(specificMethod).submit( + AsyncTaskExecutor executor = determineAsyncExecutor(specificMethod); + if (executor == null) { + throw new IllegalStateException( + "No executor specified and no default executor set on AsyncExecutionInterceptor either"); + } + + Future result = executor.submit( new Callable() { public Object call() throws Exception { try { @@ -111,8 +117,8 @@ public Object call() throws Exception { * Subclasses may override to provide support for extracting qualifier information, * e.g. via an annotation on the given method. * @return always {@code null} - * @see #determineAsyncExecutor(Method) * @since 3.1.2 + * @see #determineAsyncExecutor(Method) */ @Override protected String getExecutorQualifier(Method method) { 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 a5d1f50805..f8b19cb71f 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-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -151,7 +151,7 @@ public class CustomizableTraceInterceptor extends AbstractTraceInterceptor { /** * The {@code Set} of allowed placeholders. */ - private static final Set ALLOWED_PLACEHOLDERS = + private static final Set ALLOWED_PLACEHOLDERS = new Constants(CustomizableTraceInterceptor.class).getValues("PLACEHOLDER_"); @@ -258,7 +258,7 @@ protected Object invokeUnderTrace(MethodInvocation invocation, Log logger) throw return returnValue; } catch (Throwable ex) { - if(stopWatch.isRunning()) { + if (stopWatch.isRunning()) { stopWatch.stop(); } exitThroughException = true; @@ -268,7 +268,7 @@ protected Object invokeUnderTrace(MethodInvocation invocation, Log logger) throw } finally { if (!exitThroughException) { - if(stopWatch.isRunning()) { + if (stopWatch.isRunning()) { stopWatch.stop(); } writeToLog(logger, @@ -325,18 +325,19 @@ protected String replacePlaceholders(String message, MethodInvocation methodInvo while (matcher.find()) { String match = matcher.group(); if (PLACEHOLDER_METHOD_NAME.equals(match)) { - matcher.appendReplacement(output, escape(methodInvocation.getMethod().getName())); + matcher.appendReplacement(output, Matcher.quoteReplacement(methodInvocation.getMethod().getName())); } else if (PLACEHOLDER_TARGET_CLASS_NAME.equals(match)) { String className = getClassForLogging(methodInvocation.getThis()).getName(); - matcher.appendReplacement(output, escape(className)); + matcher.appendReplacement(output, Matcher.quoteReplacement(className)); } else if (PLACEHOLDER_TARGET_CLASS_SHORT_NAME.equals(match)) { String shortName = ClassUtils.getShortName(getClassForLogging(methodInvocation.getThis())); - matcher.appendReplacement(output, escape(shortName)); + matcher.appendReplacement(output, Matcher.quoteReplacement(shortName)); } else if (PLACEHOLDER_ARGUMENTS.equals(match)) { - matcher.appendReplacement(output, escape(StringUtils.arrayToCommaDelimitedString(methodInvocation.getArguments()))); + matcher.appendReplacement(output, + Matcher.quoteReplacement(StringUtils.arrayToCommaDelimitedString(methodInvocation.getArguments()))); } else if (PLACEHOLDER_ARGUMENT_TYPES.equals(match)) { appendArgumentTypes(methodInvocation, matcher, output); @@ -345,7 +346,7 @@ else if (PLACEHOLDER_RETURN_VALUE.equals(match)) { appendReturnValue(methodInvocation, matcher, output, returnValue); } else if (throwable != null && PLACEHOLDER_EXCEPTION.equals(match)) { - matcher.appendReplacement(output, escape(throwable.toString())); + matcher.appendReplacement(output, Matcher.quoteReplacement(throwable.toString())); } else if (PLACEHOLDER_INVOCATION_TIME.equals(match)) { matcher.appendReplacement(output, Long.toString(invocationTime)); @@ -379,7 +380,7 @@ else if (returnValue == null) { matcher.appendReplacement(output, "null"); } else { - matcher.appendReplacement(output, escape(returnValue.toString())); + matcher.appendReplacement(output, Matcher.quoteReplacement(returnValue.toString())); } } @@ -394,12 +395,13 @@ else if (returnValue == null) { * @param output the {@code StringBuffer} containing the output */ private void appendArgumentTypes(MethodInvocation methodInvocation, Matcher matcher, StringBuffer output) { - Class[] argumentTypes = methodInvocation.getMethod().getParameterTypes(); + Class[] argumentTypes = methodInvocation.getMethod().getParameterTypes(); String[] argumentTypeShortNames = new String[argumentTypes.length]; for (int i = 0; i < argumentTypeShortNames.length; i++) { argumentTypeShortNames[i] = ClassUtils.getShortName(argumentTypes[i]); } - matcher.appendReplacement(output, escape(StringUtils.arrayToCommaDelimitedString(argumentTypeShortNames))); + matcher.appendReplacement(output, + Matcher.quoteReplacement(StringUtils.arrayToCommaDelimitedString(argumentTypeShortNames))); } /** @@ -417,27 +419,4 @@ private void checkForInvalidPlaceholders(String message) throws IllegalArgumentE } } - /** - * Replaces {@code $} in inner class names with {@code \$}. - *

This code is equivalent to JDK 1.5's {@code quoteReplacement} - * method in the Matcher class itself. We're keeping our own version - * here for JDK 1.4 compliance reasons only. - */ - private String escape(String input) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < input.length(); i++) { - char c = input.charAt(i); - if (c == '\\') { - sb.append("\\\\"); - } - else if (c == '$') { - sb.append("\\$"); - } - else { - sb.append(c); - } - } - return sb.toString(); - } - } 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 34bc7b9b74..f6eae79e83 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-2012 the original author or authors. + * Copyright 2002-2014 the original author 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 void setBeanFactory(BeanFactory beanFactory) { pf.copyFrom(this); pf.setTargetSource(this.scopedTargetSource); - Class beanType = beanFactory.getType(this.targetBeanName); + Class beanType = beanFactory.getType(this.targetBeanName); if (beanType == null) { throw new IllegalStateException("Cannot create scoped proxy for bean '" + this.targetBeanName + "': Target type could not be determined at the time of proxy creation."); @@ -122,10 +122,7 @@ public Class getObjectType() { if (this.proxy != null) { return this.proxy.getClass(); } - if (this.scopedTargetSource != null) { - return this.scopedTargetSource.getTargetClass(); - } - return null; + return this.scopedTargetSource.getTargetClass(); } public boolean isSingleton() { 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 fdd0253d6e..2f2a89887c 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-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,7 @@ public abstract class ScopedProxyUtils { /** - * Generates a scoped proxy for the supplied target bean, registering the target + * Generate a scoped proxy for the supplied target bean, registering the target * bean with an internal name and setting 'targetBeanName' on the scoped proxy. * @param definition the original bean definition * @param registry the bean definition registry @@ -50,20 +50,20 @@ public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder defini String originalBeanName = definition.getBeanName(); BeanDefinition targetDefinition = definition.getBeanDefinition(); + String targetBeanName = getTargetBeanName(originalBeanName); // Create a scoped proxy definition for the original bean name, // "hiding" the target bean in an internal target definition. RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class); - proxyDefinition.setOriginatingBeanDefinition(definition.getBeanDefinition()); + proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName)); + proxyDefinition.setOriginatingBeanDefinition(targetDefinition); proxyDefinition.setSource(definition.getSource()); proxyDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - String targetBeanName = getTargetBeanName(originalBeanName); proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName); - if (proxyTargetClass) { targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE); - // ScopedFactoryBean's "proxyTargetClass" default is TRUE, so we don't need to set it explicitly here. + // ScopedProxyFactoryBean's "proxyTargetClass" default is TRUE, so we don't need to set it explicitly here. } else { proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE); 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 3715a9ca70..b6eb42d935 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/AbstractBeanFactoryPointcutAdvisor.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/AbstractBeanFactoryPointcutAdvisor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-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. @@ -23,6 +23,7 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.support.AbstractBeanFactory; import org.springframework.util.Assert; /** @@ -45,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(); @@ -71,8 +72,23 @@ public String getAdviceBeanName() { public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; + resetAdviceMonitor(); } + private void resetAdviceMonitor() { + if (this.beanFactory instanceof AbstractBeanFactory) { + this.adviceMonitor = ((AbstractBeanFactory) this.beanFactory).getSingletonMutex(); + } + else { + this.adviceMonitor = new Object(); + } + } + + /** + * Specify a particular instance of the target advice directly, + * avoiding lazy resolution in {@link #getAdvice()}. + * @since 3.1 + */ public void setAdvice(Advice advice) { synchronized (this.adviceMonitor) { this.advice = advice; @@ -80,18 +96,42 @@ public void setAdvice(Advice advice) { } 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; } } @Override public String toString() { - return getClass().getName() + ": advice bean '" + getAdviceBeanName() + "'"; + StringBuilder sb = new StringBuilder(getClass().getName()); + sb.append(": advice "); + if (this.adviceBeanName != null) { + sb.append("bean '").append(this.adviceBeanName).append("'"); + } + else { + sb.append(this.advice); + } + return sb.toString(); } @@ -104,7 +144,7 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound ois.defaultReadObject(); // Initialize transient fields. - this.adviceMonitor = new Object(); + resetAdviceMonitor(); } } 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 b7527ca6af..fd712126d8 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-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,17 +29,16 @@ *

    *
  • pattern: regular expression for the fully-qualified method names to match. * The exact regexp syntax will depend on the subclass (e.g. Perl5 regular expressions) - *
  • patterns: alternative property taking a String array of patterns. The result will - * be the union of these patterns. + *
  • patterns: alternative property taking a String array of patterns. + * The result will be the union of these patterns. *
* *

Note: the regular expressions must be a match. For example, * {@code .*get.*} will match com.mycom.Foo.getBar(). * {@code get.*} will not. * - *

This base class is serializable. Subclasses should declare all fields transient - * - the initPatternRepresentation method in this class will be invoked again on the - * client side on deserialization. + *

This base class is serializable. Subclasses should declare all fields transient; + * the {@link #initPatternRepresentation} method will be invoked again on deserialization. * * @author Rod Johnson * @author Juergen Hoeller @@ -51,10 +50,14 @@ public abstract class AbstractRegexpMethodPointcut extends StaticMethodMatcherPointcut implements Serializable { - /** Regular expressions to match */ + /** + * Regular expressions to match. + */ private String[] patterns = new String[0]; - /** Regular expressions not to match */ + /** + * Regular expressions not to match. + */ private String[] excludedPatterns = new String[0]; @@ -64,15 +67,15 @@ public abstract class AbstractRegexpMethodPointcut extends StaticMethodMatcherPo * @see #setPatterns */ public void setPattern(String pattern) { - setPatterns(new String[] {pattern}); + setPatterns(pattern); } /** * Set the regular expressions defining methods to match. - * Matching will be the union of all these; if any match, - * the pointcut matches. + * Matching will be the union of all these; if any match, the pointcut matches. + * @see #setPattern */ - public void setPatterns(String[] patterns) { + public void setPatterns(String... patterns) { Assert.notEmpty(patterns, "'patterns' must not be empty"); this.patterns = new String[patterns.length]; for (int i = 0; i < patterns.length; i++) { @@ -94,15 +97,15 @@ public String[] getPatterns() { * @see #setExcludedPatterns */ public void setExcludedPattern(String excludedPattern) { - setExcludedPatterns(new String[] {excludedPattern}); + setExcludedPatterns(excludedPattern); } /** * Set the regular expressions defining methods to match for exclusion. - * Matching will be the union of all these; if any match, - * the pointcut matches. + * Matching will be the union of all these; if any match, the pointcut matches. + * @see #setExcludedPattern */ - public void setExcludedPatterns(String[] excludedPatterns) { + public void setExcludedPatterns(String... excludedPatterns) { Assert.notEmpty(excludedPatterns, "'excludedPatterns' must not be empty"); this.excludedPatterns = new String[excludedPatterns.length]; for (int i = 0; i < excludedPatterns.length; i++) { @@ -124,7 +127,7 @@ public String[] getExcludedPatterns() { * of the target class as well as against the method's declaring class, * plus the name of the method. */ - public boolean matches(Method method, Class targetClass) { + public boolean matches(Method method, Class targetClass) { return ((targetClass != null && matchesPattern(targetClass.getName() + "." + method.getName())) || matchesPattern(method.getDeclaringClass().getName() + "." + method.getName())); } @@ -172,18 +175,18 @@ protected boolean matchesPattern(String signatureString) { protected abstract void initExcludedPatternRepresentation(String[] patterns) throws IllegalArgumentException; /** - * Does the pattern at the given index match this string? - * @param pattern {@code String} pattern to match - * @param patternIndex index of pattern from 0 - * @return {@code true} if there is a match, else {@code false}. + * Does the pattern at the given index match the given String? + * @param pattern the {@code String} pattern to match + * @param patternIndex index of pattern (starting from 0) + * @return {@code true} if there is a match, {@code false} otherwise */ protected abstract boolean matches(String pattern, int patternIndex); /** - * Does the exclusion pattern at the given index match this string? - * @param pattern {@code String} pattern to match. - * @param patternIndex index of pattern starting from 0. - * @return {@code true} if there is a match, else {@code false}. + * Does the exclusion pattern at the given index match the given String? + * @param pattern the {@code String} pattern to match + * @param patternIndex index of pattern (starting from 0) + * @return {@code true} if there is a match, {@code false} otherwise */ protected abstract boolean matchesExclusion(String pattern, int patternIndex); diff --git a/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java b/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java index fdd985191e..ac4a4ecad9 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -215,7 +215,7 @@ public static boolean canApply(Pointcut pc, Class targetClass, boolean hasInt introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher; } - Set classes = new HashSet(ClassUtils.getAllInterfacesForClassAsSet(targetClass)); + Set classes = new LinkedHashSet(ClassUtils.getAllInterfacesForClassAsSet(targetClass)); classes.add(targetClass); for (Class clazz : classes) { Method[] methods = clazz.getMethods(); diff --git a/spring-aop/src/main/java/org/springframework/aop/support/ClassFilters.java b/spring-aop/src/main/java/org/springframework/aop/support/ClassFilters.java index 1dea2cb113..58b19c9029 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/ClassFilters.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/ClassFilters.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,8 +23,7 @@ import org.springframework.util.ObjectUtils; /** - * Static utility methods for composing - * {@link org.springframework.aop.ClassFilter ClassFilters}. + * Static utility methods for composing {@link ClassFilter ClassFilters}. * * @author Rod Johnson * @author Rob Harrop @@ -96,9 +95,9 @@ public UnionClassFilter(ClassFilter[] filters) { this.filters = filters; } - public boolean matches(Class clazz) { - for (int i = 0; i < this.filters.length; i++) { - if (this.filters[i].matches(clazz)) { + public boolean matches(Class clazz) { + for (ClassFilter filter : this.filters) { + if (filter.matches(clazz)) { return true; } } @@ -130,9 +129,9 @@ public IntersectionClassFilter(ClassFilter[] filters) { this.filters = filters; } - public boolean matches(Class clazz) { - for (int i = 0; i < this.filters.length; i++) { - if (!this.filters[i].matches(clazz)) { + public boolean matches(Class clazz) { + for (ClassFilter filter : this.filters) { + if (!filter.matches(clazz)) { return false; } } 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 b537a6ab8e..42ae067dcf 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-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -178,7 +178,6 @@ public MethodMatcher getMethodMatcher() { return this.methodMatcher; } - @Override public boolean equals(Object other) { if (this == other) { @@ -187,7 +186,6 @@ 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); @@ -207,8 +205,7 @@ public int hashCode() { @Override public String toString() { - return "ComposablePointcut: ClassFilter [" + this.classFilter + - "], MethodMatcher [" + this.methodMatcher + "]"; + return "ComposablePointcut: " + this.classFilter + ", " +this.methodMatcher; } } 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 e1aad6a170..d1af2ec4a7 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-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ package org.springframework.aop.support; import java.io.Serializable; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Set; import org.aopalliance.aop.Advice; @@ -43,7 +43,7 @@ public class DefaultIntroductionAdvisor implements IntroductionAdvisor, ClassFil private final Advice advice; - private final Set interfaces = new HashSet(); + private final Set interfaces = new LinkedHashSet(); private int order = Integer.MAX_VALUE; @@ -68,11 +68,11 @@ public DefaultIntroductionAdvisor(Advice advice, IntroductionInfo introductionIn Assert.notNull(advice, "Advice must not be null"); this.advice = advice; if (introductionInfo != null) { - Class[] introducedInterfaces = introductionInfo.getInterfaces(); + Class[] introducedInterfaces = introductionInfo.getInterfaces(); if (introducedInterfaces.length == 0) { throw new IllegalArgumentException("IntroductionAdviceSupport implements no interfaces"); } - for (Class ifc : introducedInterfaces) { + for (Class ifc : introducedInterfaces) { addInterface(ifc); } } @@ -83,7 +83,7 @@ public DefaultIntroductionAdvisor(Advice advice, IntroductionInfo introductionIn * @param advice the Advice to apply * @param intf the interface to introduce */ - public DefaultIntroductionAdvisor(DynamicIntroductionAdvice advice, Class intf) { + public DefaultIntroductionAdvisor(DynamicIntroductionAdvice advice, Class intf) { Assert.notNull(advice, "Advice must not be null"); this.advice = advice; addInterface(intf); @@ -94,7 +94,7 @@ public DefaultIntroductionAdvisor(DynamicIntroductionAdvice advice, Class intf) * Add the specified interface to the list of interfaces to introduce. * @param intf the interface to introduce */ - public void addInterface(Class intf) { + public void addInterface(Class intf) { Assert.notNull(intf, "Interface must not be null"); if (!intf.isInterface()) { throw new IllegalArgumentException("Specified class [" + intf.getName() + "] must be an interface"); @@ -102,12 +102,13 @@ public void addInterface(Class intf) { this.interfaces.add(intf); } - public Class[] getInterfaces() { - return this.interfaces.toArray(new Class[this.interfaces.size()]); + public Class[] getInterfaces() { + return this.interfaces.toArray(new Class[this.interfaces.size()]); } + @Override public void validateInterfaces() throws IllegalArgumentException { - for (Class ifc : this.interfaces) { + for (Class ifc : this.interfaces) { if (this.advice instanceof DynamicIntroductionAdvice && !((DynamicIntroductionAdvice) this.advice).implementsInterface(ifc)) { throw new IllegalArgumentException("DynamicIntroductionAdvice [" + this.advice + "] " + @@ -138,7 +139,7 @@ public ClassFilter getClassFilter() { return this; } - public boolean matches(Class clazz) { + public boolean matches(Class clazz) { return true; } 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 c8807f7639..73adc67b16 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-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import java.io.ObjectInputStream; import java.io.Serializable; import java.lang.reflect.Method; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -43,7 +43,7 @@ @SuppressWarnings("serial") public class IntroductionInfoSupport implements IntroductionInfo, Serializable { - protected final Set publishedInterfaces = new HashSet(); + protected final Set publishedInterfaces = new LinkedHashSet(); private transient Map rememberedMethods = new ConcurrentHashMap(32); @@ -55,12 +55,12 @@ public class IntroductionInfoSupport implements IntroductionInfo, Serializable { *

Does nothing if the interface is not implemented by the delegate. * @param intf the interface to suppress */ - public void suppressInterface(Class intf) { + public void suppressInterface(Class intf) { this.publishedInterfaces.remove(intf); } - public Class[] getInterfaces() { - return this.publishedInterfaces.toArray(new Class[this.publishedInterfaces.size()]); + public Class[] getInterfaces() { + return this.publishedInterfaces.toArray(new Class[this.publishedInterfaces.size()]); } /** @@ -68,8 +68,8 @@ public Class[] getInterfaces() { * @param ifc the interface to check * @return whether the interface is part of this introduction */ - public boolean implementsInterface(Class ifc) { - for (Class pubIfc : this.publishedInterfaces) { + public boolean implementsInterface(Class ifc) { + for (Class pubIfc : this.publishedInterfaces) { if (ifc.isInterface() && ifc.isAssignableFrom(pubIfc)) { return true; } diff --git a/spring-aop/src/main/java/org/springframework/aop/support/MethodMatchers.java b/spring-aop/src/main/java/org/springframework/aop/support/MethodMatchers.java index be8b5bca7f..3489e1b444 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/MethodMatchers.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/MethodMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,11 @@ import org.springframework.util.Assert; /** - * Static utility methods for composing - * {@link org.springframework.aop.MethodMatcher MethodMatchers}. + * Static utility methods for composing {@link MethodMatcher MethodMatchers}. * - *

A MethodMatcher may be evaluated statically (based on method - * and target class) or need further evaluation dynamically - * (based on arguments at the time of method invocation). + *

A MethodMatcher may be evaluated statically (based on method and target + * class) or need further evaluation dynamically (based on arguments at the + * time of method invocation). * * @author Rod Johnson * @author Rob Harrop @@ -88,7 +87,7 @@ public static MethodMatcher intersection(MethodMatcher mm1, MethodMatcher mm2) { * asking is the subject on one or more introductions; {@code false} otherwise * @return whether or not this method matches statically */ - public static boolean matches(MethodMatcher mm, Method method, Class targetClass, boolean hasIntroductions) { + public static boolean matches(MethodMatcher mm, Method method, Class targetClass, boolean hasIntroductions) { Assert.notNull(mm, "MethodMatcher must not be null"); return ((mm instanceof IntroductionAwareMethodMatcher && ((IntroductionAwareMethodMatcher) mm).matches(method, targetClass, hasIntroductions)) || @@ -102,8 +101,9 @@ public static boolean matches(MethodMatcher mm, Method method, Class targetClass @SuppressWarnings("serial") private static class UnionMethodMatcher implements IntroductionAwareMethodMatcher, Serializable { - private MethodMatcher mm1; - private MethodMatcher mm2; + private final MethodMatcher mm1; + + private final MethodMatcher mm2; public UnionMethodMatcher(MethodMatcher mm1, MethodMatcher mm2) { Assert.notNull(mm1, "First MethodMatcher must not be null"); @@ -112,21 +112,21 @@ public UnionMethodMatcher(MethodMatcher mm1, MethodMatcher mm2) { this.mm2 = mm2; } - public boolean matches(Method method, Class targetClass, boolean hasIntroductions) { + public boolean matches(Method method, Class targetClass, boolean hasIntroductions) { return (matchesClass1(targetClass) && MethodMatchers.matches(this.mm1, method, targetClass, hasIntroductions)) || (matchesClass2(targetClass) && MethodMatchers.matches(this.mm2, method, targetClass, hasIntroductions)); } - public boolean matches(Method method, Class targetClass) { + public boolean matches(Method method, Class targetClass) { return (matchesClass1(targetClass) && this.mm1.matches(method, targetClass)) || (matchesClass2(targetClass) && this.mm2.matches(method, targetClass)); } - protected boolean matchesClass1(Class targetClass) { + protected boolean matchesClass1(Class targetClass) { return true; } - protected boolean matchesClass2(Class targetClass) { + protected boolean matchesClass2(Class targetClass) { return true; } @@ -134,7 +134,7 @@ public boolean isRuntime() { return this.mm1.isRuntime() || this.mm2.isRuntime(); } - public boolean matches(Method method, Class targetClass, Object[] args) { + public boolean matches(Method method, Class targetClass, Object[] args) { return this.mm1.matches(method, targetClass, args) || this.mm2.matches(method, targetClass, args); } @@ -168,6 +168,7 @@ public int hashCode() { private static class ClassFilterAwareUnionMethodMatcher extends UnionMethodMatcher { private final ClassFilter cf1; + private final ClassFilter cf2; public ClassFilterAwareUnionMethodMatcher(MethodMatcher mm1, ClassFilter cf1, MethodMatcher mm2, ClassFilter cf2) { @@ -177,12 +178,12 @@ public ClassFilterAwareUnionMethodMatcher(MethodMatcher mm1, ClassFilter cf1, Me } @Override - protected boolean matchesClass1(Class targetClass) { + protected boolean matchesClass1(Class targetClass) { return this.cf1.matches(targetClass); } @Override - protected boolean matchesClass2(Class targetClass) { + protected boolean matchesClass2(Class targetClass) { return this.cf2.matches(targetClass); } @@ -191,11 +192,17 @@ public boolean equals(Object other) { if (this == other) { return true; } - if (!(other instanceof ClassFilterAwareUnionMethodMatcher)) { + if (!super.equals(other)) { return false; } - ClassFilterAwareUnionMethodMatcher that = (ClassFilterAwareUnionMethodMatcher) other; - return (this.cf1.equals(that.cf1) && this.cf2.equals(that.cf2) && super.equals(other)); + ClassFilter otherCf1 = ClassFilter.TRUE; + ClassFilter otherCf2 = ClassFilter.TRUE; + if (other instanceof ClassFilterAwareUnionMethodMatcher) { + ClassFilterAwareUnionMethodMatcher cfa = (ClassFilterAwareUnionMethodMatcher) other; + otherCf1 = cfa.cf1; + otherCf2 = cfa.cf2; + } + return (this.cf1.equals(otherCf1) && this.cf2.equals(otherCf2)); } } @@ -206,8 +213,9 @@ public boolean equals(Object other) { @SuppressWarnings("serial") private static class IntersectionMethodMatcher implements IntroductionAwareMethodMatcher, Serializable { - private MethodMatcher mm1; - private MethodMatcher mm2; + private final MethodMatcher mm1; + + private final MethodMatcher mm2; public IntersectionMethodMatcher(MethodMatcher mm1, MethodMatcher mm2) { Assert.notNull(mm1, "First MethodMatcher must not be null"); @@ -216,12 +224,12 @@ public IntersectionMethodMatcher(MethodMatcher mm1, MethodMatcher mm2) { this.mm2 = mm2; } - public boolean matches(Method method, Class targetClass, boolean hasIntroductions) { + public boolean matches(Method method, Class targetClass, boolean hasIntroductions) { return MethodMatchers.matches(this.mm1, method, targetClass, hasIntroductions) && MethodMatchers.matches(this.mm2, method, targetClass, hasIntroductions); } - public boolean matches(Method method, Class targetClass) { + public boolean matches(Method method, Class targetClass) { return this.mm1.matches(method, targetClass) && this.mm2.matches(method, targetClass); } @@ -229,7 +237,7 @@ public boolean isRuntime() { return this.mm1.isRuntime() || this.mm2.isRuntime(); } - public boolean matches(Method method, Class targetClass, Object[] args) { + public boolean matches(Method method, Class targetClass, Object[] args) { // Because a dynamic intersection may be composed of a static and dynamic part, // we must avoid calling the 3-arg matches method on a dynamic matcher, as // it will probably be an unsupported operation. 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 69389d0bf5..05577f7d6c 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-2012 the original author or authors. + * Copyright 2002-2014 the original author 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 class NameMatchMethodPointcut extends StaticMethodMatcherPointcut impleme * @see #setMappedNames */ public void setMappedName(String mappedName) { - setMappedNames(new String[] {mappedName}); + setMappedNames(mappedName); } /** @@ -55,7 +55,7 @@ public void setMappedName(String mappedName) { * Matching will be the union of all these; if any match, * the pointcut matches. */ - public void setMappedNames(String[] mappedNames) { + public void setMappedNames(String... mappedNames) { this.mappedNames = new LinkedList(); if (mappedNames != null) { this.mappedNames.addAll(Arrays.asList(mappedNames)); @@ -77,7 +77,8 @@ public NameMatchMethodPointcut addMethodName(String name) { } - public boolean matches(Method method, Class targetClass) { + @Override + public boolean matches(Method method, Class targetClass) { for (String mappedName : this.mappedNames) { if (mappedName.equals(method.getName()) || isMatch(method.getName(), mappedName)) { return true; diff --git a/spring-aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcutAdvisor.java b/spring-aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcutAdvisor.java index c2f85b5881..7e824e65f0 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcutAdvisor.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcutAdvisor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,7 +68,7 @@ public void setMappedName(String mappedName) { * the pointcut matches. * @see NameMatchMethodPointcut#setMappedNames */ - public void setMappedNames(String[] mappedNames) { + public void setMappedNames(String... mappedNames) { this.pointcut.setMappedNames(mappedNames); } diff --git a/spring-aop/src/main/java/org/springframework/aop/support/RegexpMethodPointcutAdvisor.java b/spring-aop/src/main/java/org/springframework/aop/support/RegexpMethodPointcutAdvisor.java index 1edb2d746b..bbbd81156f 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/RegexpMethodPointcutAdvisor.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/RegexpMethodPointcutAdvisor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -98,7 +98,7 @@ public RegexpMethodPointcutAdvisor(String[] patterns, Advice advice) { * @see #setPatterns */ public void setPattern(String pattern) { - setPatterns(new String[] {pattern}); + setPatterns(pattern); } /** @@ -108,7 +108,7 @@ public void setPattern(String pattern) { * patterns matches, the pointcut matches. * @see AbstractRegexpMethodPointcut#setPatterns */ - public void setPatterns(String[] patterns) { + public void setPatterns(String... patterns) { this.patterns = patterns; } diff --git a/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationClassFilter.java b/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationClassFilter.java index 9136c8974e..ea8a3adf15 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationClassFilter.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationClassFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,10 +59,32 @@ public AnnotationClassFilter(Class annotationType, boolean } - public boolean matches(Class clazz) { + public boolean matches(Class clazz) { return (this.checkInherited ? (AnnotationUtils.findAnnotation(clazz, this.annotationType) != null) : clazz.isAnnotationPresent(this.annotationType)); } + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof AnnotationClassFilter)) { + return false; + } + AnnotationClassFilter otherCf = (AnnotationClassFilter) other; + return (this.annotationType.equals(otherCf.annotationType) && this.checkInherited == otherCf.checkInherited); + } + + @Override + public int hashCode() { + return this.annotationType.hashCode(); + } + + @Override + public String toString() { + return getClass().getName() + ": " + this.annotationType; + } + } 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 acdbbeff88..5bc54af587 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-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import org.springframework.aop.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 @@ -98,6 +99,36 @@ public MethodMatcher getMethodMatcher() { return this.methodMatcher; } + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof AnnotationMatchingPointcut)) { + return false; + } + AnnotationMatchingPointcut that = (AnnotationMatchingPointcut) other; + return ObjectUtils.nullSafeEquals(that.classFilter, this.classFilter) && + ObjectUtils.nullSafeEquals(that.methodMatcher, this.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; + } + + @Override + public String toString() { + return "AnnotationMatchingPointcut: " + this.classFilter + ", " +this.methodMatcher; + } + /** * Factory method for an AnnotationMatchingPointcut that matches diff --git a/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMethodMatcher.java b/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMethodMatcher.java index 4f4dd6c391..15f83f7b11 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMethodMatcher.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMethodMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -73,4 +73,9 @@ public int hashCode() { return this.annotationType.hashCode(); } + @Override + public String toString() { + return getClass().getName() + ": " + this.annotationType; + } + } 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 3192fab5fe..8967162564 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-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,6 @@ import org.springframework.aop.TargetSource; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; /** @@ -48,8 +47,7 @@ * @see ThreadLocalTargetSource * @see CommonsPoolTargetSource */ -public abstract class AbstractBeanFactoryBasedTargetSource - implements TargetSource, BeanFactoryAware, Serializable { +public abstract class AbstractBeanFactoryBasedTargetSource implements TargetSource, BeanFactoryAware, Serializable { /** use serialVersionUID from Spring 1.2.7 for interoperability */ private static final long serialVersionUID = -4721607536018568393L; @@ -97,7 +95,7 @@ public String getTargetBeanName() { *

Default is to detect the type automatically, through a {@code getType} * call on the BeanFactory (or even a full {@code getBean} call as fallback). */ - public void setTargetClass(Class targetClass) { + public void setTargetClass(Class targetClass) { this.targetClass = targetClass; } @@ -107,7 +105,7 @@ public void setTargetClass(Class targetClass) { */ public void setBeanFactory(BeanFactory beanFactory) { if (this.targetBeanName == null) { - throw new IllegalStateException("Property'targetBeanName' is required"); + throw new IllegalStateException("Property 'targetBeanName' is required"); } this.beanFactory = beanFactory; } @@ -181,8 +179,7 @@ public int hashCode() { @Override public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(ClassUtils.getShortName(getClass())); + StringBuilder sb = new StringBuilder(getClass().getSimpleName()); sb.append(" for target bean '").append(this.targetBeanName).append("'"); if (this.targetClass != null) { sb.append(" of type [").append(this.targetClass.getName()).append("]"); diff --git a/spring-aop/src/main/java/org/springframework/aop/target/AbstractPoolingTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/AbstractPoolingTargetSource.java index 91e1b72ee7..545d45052c 100644 --- a/spring-aop/src/main/java/org/springframework/aop/target/AbstractPoolingTargetSource.java +++ b/spring-aop/src/main/java/org/springframework/aop/target/AbstractPoolingTargetSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,7 @@ * @see #releaseTarget * @see #destroy */ +@SuppressWarnings("serial") public abstract class AbstractPoolingTargetSource extends AbstractPrototypeBasedTargetSource implements PoolingConfig, DisposableBean { 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 4880d5119b..24e0286e97 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-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,7 @@ * @see ThreadLocalTargetSource * @see CommonsPoolTargetSource */ +@SuppressWarnings("serial") public abstract class AbstractPrototypeBasedTargetSource extends AbstractBeanFactoryBasedTargetSource { @Override diff --git a/spring-aop/src/main/java/org/springframework/aop/target/CommonsPoolTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/CommonsPoolTargetSource.java index 47a35f756e..a48621ae00 100644 --- a/spring-aop/src/main/java/org/springframework/aop/target/CommonsPoolTargetSource.java +++ b/spring-aop/src/main/java/org/springframework/aop/target/CommonsPoolTargetSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,8 +24,8 @@ import org.springframework.core.Constants; /** - * TargetSource implementation that holds objects in a configurable - * Jakarta Commons Pool. + * {@link org.springframework.aop.TargetSource} implementation that holds + * objects in a configurable Apache Commons Pool. * *

By default, an instance of {@code GenericObjectPool} is created. * Subclasses may change the type of {@code ObjectPool} used by @@ -38,10 +38,12 @@ * of configuration properties that are relevant to your chosen implementation. * *

The {@code testOnBorrow}, {@code testOnReturn} and {@code testWhileIdle} - * properties are explictly not mirrored because the implementation of + * properties are explicitly not mirrored because the implementation of * {@code PoolableObjectFactory} used by this class does not implement - * meaningful validation. All exposed Commons Pool properties use the corresponding - * Commons Pool defaults: for example, + * meaningful validation. All exposed Commons Pool properties use the + * corresponding Commons Pool defaults. + * + *

Compatible with Apache Commons Pool 1.5.x and 1.6. * * @author Rod Johnson * @author Rob Harrop @@ -55,10 +57,8 @@ * @see #setTimeBetweenEvictionRunsMillis * @see #setMinEvictableIdleTimeMillis */ -public class CommonsPoolTargetSource extends AbstractPoolingTargetSource - implements PoolableObjectFactory { - - private static final long serialVersionUID = 1L; +@SuppressWarnings("serial") +public class CommonsPoolTargetSource extends AbstractPoolingTargetSource implements PoolableObjectFactory { private static final Constants constants = new Constants(GenericObjectPool.class); diff --git a/spring-aop/src/main/java/org/springframework/aop/target/PrototypeTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/PrototypeTargetSource.java index 81642f6b2e..57604a0853 100644 --- a/spring-aop/src/main/java/org/springframework/aop/target/PrototypeTargetSource.java +++ b/spring-aop/src/main/java/org/springframework/aop/target/PrototypeTargetSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,9 +19,11 @@ import org.springframework.beans.BeansException; /** - * TargetSource that creates a new instance of the target bean for each - * request, destroying each instance on release (after each request). - * Obtains bean instances from its containing + * {@link org.springframework.aop.TargetSource} implementation that + * creates a new instance of the target bean for each request, + * destroying each instance on release (after each request). + * + *

Obtains bean instances from its containing * {@link org.springframework.beans.factory.BeanFactory}. * * @author Rod Johnson @@ -29,10 +31,9 @@ * @see #setBeanFactory * @see #setTargetBeanName */ +@SuppressWarnings("serial") public class PrototypeTargetSource extends AbstractPrototypeBasedTargetSource { - private static final long serialVersionUID = 1L; - /** * Obtain a new prototype instance for every call. * @see #newPrototypeInstance() diff --git a/spring-aop/src/main/java/org/springframework/aop/target/ThreadLocalTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/ThreadLocalTargetSource.java index 7b100fe0da..a33681fd3c 100644 --- a/spring-aop/src/main/java/org/springframework/aop/target/ThreadLocalTargetSource.java +++ b/spring-aop/src/main/java/org/springframework/aop/target/ThreadLocalTargetSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,9 +27,10 @@ import org.springframework.core.NamedThreadLocal; /** - * Alternative to an object pool. This TargetSource uses a threading model in which - * every thread has its own copy of the target. There's no contention for targets. - * Target object creation is kept to a minimum on the running server. + * Alternative to an object pool. This {@link org.springframework.aop.TargetSource} + * uses a threading model in which every thread has its own copy of the target. + * There's no contention for targets. Target object creation is kept to a minimum + * on the running server. * *

Application code is written as to a normal pool; callers can't assume they * will be dealing with the same instance in invocations in different threads. @@ -47,11 +48,10 @@ * @see ThreadLocalTargetSourceStats * @see org.springframework.beans.factory.DisposableBean#destroy() */ +@SuppressWarnings("serial") public class ThreadLocalTargetSource extends AbstractPrototypeBasedTargetSource implements ThreadLocalTargetSourceStats, DisposableBean { - private static final long serialVersionUID = 1L; - /** * ThreadLocal holding the target associated with the current * thread. Unlike most ThreadLocals, which are static, this variable @@ -80,10 +80,8 @@ public Object getTarget() throws BeansException { Object target = this.targetInThread.get(); if (target == null) { if (logger.isDebugEnabled()) { - logger.debug("No target for prototype '" + getTargetBeanName() + - "' bound to thread: " + - "creating one and binding it to thread '" + - Thread.currentThread().getName() + "'"); + logger.debug("No target for prototype '" + getTargetBeanName() + "' bound to thread: " + + "creating one and binding it to thread '" + Thread.currentThread().getName() + "'"); } // Associate target with ThreadLocal. target = newPrototypeInstance(); diff --git a/spring-aop/src/test/java/org/springframework/aop/support/MethodMatchersTests.java b/spring-aop/src/test/java/org/springframework/aop/support/MethodMatchersTests.java index 8f06791335..0f4fd8d3ce 100644 --- a/spring-aop/src/test/java/org/springframework/aop/support/MethodMatchersTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/support/MethodMatchersTests.java @@ -43,6 +43,7 @@ public final class MethodMatchersTests { private final Method IOTHER_ABSQUATULATE; + public MethodMatchersTests() throws Exception { EXCEPTION_GETMESSAGE = Exception.class.getMethod("getMessage", (Class[]) null); ITESTBEAN_GETAGE = ITestBean.class.getMethod("getAge", (Class[]) null); @@ -50,6 +51,7 @@ public MethodMatchersTests() throws Exception { IOTHER_ABSQUATULATE = IOther.class.getMethod("absquatulate", (Class[]) null); } + @Test public void testDefaultMatchesAll() throws Exception { MethodMatcher defaultMm = MethodMatcher.TRUE; @@ -99,23 +101,33 @@ public void testStaticMethodMatcherUnion() throws Exception { assertTrue("Matched setAge method", union.matches(ITESTBEAN_SETAGE, TestBean.class)); assertTrue("Matched getAge method", union.matches(ITESTBEAN_GETAGE, TestBean.class)); assertFalse("Didn't matched absquatulate method", union.matches(IOTHER_ABSQUATULATE, TestBean.class)); + } + @Test + public void testUnionEquals() { + MethodMatcher first = MethodMatchers.union(MethodMatcher.TRUE, MethodMatcher.TRUE); + MethodMatcher second = new ComposablePointcut(MethodMatcher.TRUE).union(new ComposablePointcut(MethodMatcher.TRUE)).getMethodMatcher(); + assertTrue(first.equals(second)); + assertTrue(second.equals(first)); } public static class StartsWithMatcher extends StaticMethodMatcher { - private String prefix; + + private final String prefix; + public StartsWithMatcher(String s) { this.prefix = s; } + @Override public boolean matches(Method m, Class targetClass) { return m.getName().startsWith(prefix); } } - private static class TestDynamicMethodMatcherWhichMatches extends DynamicMethodMatcher { + @Override public boolean matches(Method m, Class targetClass, Object[] args) { return true; @@ -123,6 +135,7 @@ public boolean matches(Method m, Class targetClass, Object[] args) { } private static class TestDynamicMethodMatcherWhichDoesNotMatch extends DynamicMethodMatcher { + @Override public boolean matches(Method m, Class targetClass, Object[] args) { return false; 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 d152719261..de49c8af7f 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,23 +28,23 @@ 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(); } + protected void increment() { + this.count++; + } + public int getCount() { return this.count; } - protected void increment() { - ++count; - } + @Override public boolean equals(Object other) { if (!(other instanceof NopInterceptor)) { return false; @@ -56,4 +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 2730d50c8e..bfa856144a 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. @@ -29,26 +29,28 @@ public class SerializablePerson implements Person, Serializable { 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 @@ -59,6 +61,8 @@ public Object echo(Object o) throws Throwable { return o; } + + @Override public boolean equals(Object other) { if (!(other instanceof SerializablePerson)) { return false; @@ -67,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-aspects/aspects.gradle b/spring-aspects/aspects.gradle index 8172830316..59a0ae615f 100644 --- a/spring-aspects/aspects.gradle +++ b/spring-aspects/aspects.gradle @@ -12,8 +12,8 @@ configurations { tasks.getByName("idea").onlyIf { false } tasks.getByName("ideaModule").onlyIf { false } -task compileJava(overwrite: true) { - dependsOn JavaPlugin.PROCESS_RESOURCES_TASK_NAME +compileJava { + actions = [] dependsOn configurations.ajc.getTaskDependencyFromProjectDependency(true, "compileJava") def outputDir = project.sourceSets.main.output.classesDir @@ -44,8 +44,8 @@ task compileJava(overwrite: true) { } } -task compileTestJava(overwrite: true) { - dependsOn JavaPlugin.PROCESS_TEST_RESOURCES_TASK_NAME +compileTestJava { + actions = [] dependsOn configurations.ajc.getTaskDependencyFromProjectDependency(true, "compileTestJava") dependsOn jar diff --git a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractDependencyInjectionAspect.aj b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractDependencyInjectionAspect.aj index fa8fc6441f..e0a9e9fad4 100644 --- a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractDependencyInjectionAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractDependencyInjectionAspect.aj @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author 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,26 +27,18 @@ import org.aspectj.lang.annotation.control.CodeGenerationHint; * @since 2.5.2 */ public abstract aspect AbstractDependencyInjectionAspect { - /** - * Select construction join points for objects to inject dependencies - */ - public abstract pointcut beanConstruction(Object bean); - /** - * Select deserialization join points for objects to inject dependencies - */ - public abstract pointcut beanDeserialization(Object bean); + private pointcut preConstructionCondition() : + leastSpecificSuperTypeConstruction() && preConstructionConfiguration(); - /** - * Select join points in a configurable bean - */ - public abstract pointcut inConfigurableBean(); + private pointcut postConstructionCondition() : + mostSpecificSubTypeConstruction() && !preConstructionConfiguration(); /** - * Select join points in beans to be configured prior to construction? - * By default, use post-construction injection matching the default in the Configurable annotation. + * Select least specific super type that is marked for DI + * (so that injection occurs only once with pre-construction injection). */ - public pointcut preConstructionConfiguration() : if(false); + public abstract pointcut leastSpecificSuperTypeConstruction(); /** * Select the most-specific initialization join point @@ -54,31 +46,36 @@ public abstract aspect AbstractDependencyInjectionAspect { */ @CodeGenerationHint(ifNameSuffix="6f1") public pointcut mostSpecificSubTypeConstruction() : - if(thisJoinPoint.getSignature().getDeclaringType() == thisJoinPoint.getThis().getClass()); + if (thisJoinPoint.getSignature().getDeclaringType() == thisJoinPoint.getThis().getClass()); /** - * Select least specific super type that is marked for DI (so that injection occurs only once with pre-construction inejection + * Select join points in beans to be configured prior to construction? + * By default, use post-construction injection matching the default in the Configurable annotation. */ - public abstract pointcut leastSpecificSuperTypeConstruction(); + public pointcut preConstructionConfiguration() : if (false); /** - * Configure the bean + * Select construction join points for objects to inject dependencies. */ - public abstract void configureBean(Object bean); + public abstract pointcut beanConstruction(Object bean); + /** + * Select deserialization join points for objects to inject dependencies. + */ + public abstract pointcut beanDeserialization(Object bean); - private pointcut preConstructionCondition() : - leastSpecificSuperTypeConstruction() && preConstructionConfiguration(); + /** + * Select join points in a configurable bean. + */ + public abstract pointcut inConfigurableBean(); - private pointcut postConstructionCondition() : - mostSpecificSubTypeConstruction() && !preConstructionConfiguration(); /** * Pre-construction configuration. */ @SuppressAjWarnings("adviceDidNotMatch") before(Object bean) : - beanConstruction(bean) && preConstructionCondition() && inConfigurableBean() { + beanConstruction(bean) && preConstructionCondition() && inConfigurableBean() { configureBean(bean); } @@ -87,7 +84,7 @@ public abstract aspect AbstractDependencyInjectionAspect { */ @SuppressAjWarnings("adviceDidNotMatch") after(Object bean) returning : - beanConstruction(bean) && postConstructionCondition() && inConfigurableBean() { + beanConstruction(bean) && postConstructionCondition() && inConfigurableBean() { configureBean(bean); } @@ -96,8 +93,14 @@ public abstract aspect AbstractDependencyInjectionAspect { */ @SuppressAjWarnings("adviceDidNotMatch") after(Object bean) returning : - beanDeserialization(bean) && inConfigurableBean() { + beanDeserialization(bean) && inConfigurableBean() { configureBean(bean); } + + /** + * Configure the given bean. + */ + public abstract void configureBean(Object bean); + } diff --git a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractInterfaceDrivenDependencyInjectionAspect.aj b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractInterfaceDrivenDependencyInjectionAspect.aj index 8270d6962d..a65be5f5bb 100644 --- a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractInterfaceDrivenDependencyInjectionAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractInterfaceDrivenDependencyInjectionAspect.aj @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author 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,49 +20,48 @@ import java.io.ObjectStreamException; import java.io.Serializable; /** - * An aspect that injects dependency into any object whose type implements the {@link ConfigurableObject} interface. - *

- * This aspect supports injecting into domain objects when they are created for the first time as well as - * upon deserialization. Subaspects need to simply provide definition for the configureBean() method. This - * method may be implemented without relying on Spring container if so desired. - *

- *

- * There are two cases that needs to be handled: + * An aspect that injects dependency into any object whose type implements the + * {@link ConfigurableObject} interface. + * + *

This aspect supports injecting into domain objects when they are created + * for the first time as well as upon deserialization. Subaspects need to simply + * provide definition for the configureBean() method. This method may be + * implemented without relying on Spring container if so desired. + * + *

There are two cases that needs to be handled: *

    - *
  1. Normal object creation via the '{@code new}' operator: this is - * taken care of by advising {@code initialization()} join points.
  2. - *
  3. Object creation through deserialization: since no constructor is - * invoked during deserialization, the aspect needs to advise a method that a - * deserialization mechanism is going to invoke. Ideally, we should not - * require user classes to implement any specific method. This implies that - * we need to introduce the chosen method. We should also handle the cases - * where the chosen method is already implemented in classes (in which case, - * the user's implementation for that method should take precedence over the - * introduced implementation). There are a few choices for the chosen method: - *
      - *
    • readObject(ObjectOutputStream): Java requires that the method must be - * {@code private}

      . Since aspects cannot introduce a private member, - * while preserving its name, this option is ruled out.
    • - *
    • readResolve(): Java doesn't pose any restriction on an access specifier. - * Problem solved! There is one (minor) limitation of this approach in - * that if a user class already has this method, that method must be - * {@code public}. However, this shouldn't be a big burden, since - * use cases that need classes to implement readResolve() (custom enums, - * for example) are unlikely to be marked as @Configurable, and - * in any case asking to make that method {@code public} should not - * pose any undue burden.
    • - *
    - * The minor collaboration needed by user classes (i.e., that the - * implementation of {@code readResolve()}, if any, must be - * {@code public}) can be lifted as well if we were to use an - * experimental feature in AspectJ - the {@code hasmethod()} PCD.
  4. + *
  5. Normal object creation via the '{@code new}' operator: this is + * taken care of by advising {@code initialization()} join points.
  6. + *
  7. Object creation through deserialization: since no constructor is + * invoked during deserialization, the aspect needs to advise a method that a + * deserialization mechanism is going to invoke. Ideally, we should not + * require user classes to implement any specific method. This implies that + * we need to introduce the chosen method. We should also handle the cases + * where the chosen method is already implemented in classes (in which case, + * the user's implementation for that method should take precedence over the + * introduced implementation). There are a few choices for the chosen method: + *
      + *
    • readObject(ObjectOutputStream): Java requires that the method must be + * {@code private}

      . Since aspects cannot introduce a private member, + * while preserving its name, this option is ruled out.
    • + *
    • readResolve(): Java doesn't pose any restriction on an access specifier. + * Problem solved! There is one (minor) limitation of this approach in + * that if a user class already has this method, that method must be + * {@code public}. However, this shouldn't be a big burden, since + * use cases that need classes to implement readResolve() (custom enums, + * for example) are unlikely to be marked as @Configurable, and + * in any case asking to make that method {@code public} should not + * pose any undue burden.
    • + *
    + * The minor collaboration needed by user classes (i.e., that the implementation of + * {@code readResolve()}, if any, must be {@code public}) can be lifted as well if we + * were to use an experimental feature in AspectJ - the {@code hasmethod()} PCD.
  8. *
- - *

- * While having type implement the {@link ConfigurableObject} interface is certainly a valid choice, an alternative - * is to use a 'declare parents' statement another aspect (a subaspect of this aspect would be a logical choice) - * that declares the classes that need to be configured by supplying the {@link ConfigurableObject} interface. - *

+ * + *

While having type implement the {@link ConfigurableObject} interface is certainly + * a valid choice, an alternative is to use a 'declare parents' statement another aspect + * (a subaspect of this aspect would be a logical choice) that declares the classes that + * need to be configured by supplying the {@link ConfigurableObject} interface. * * @author Ramnivas Laddad * @since 2.5.2 @@ -72,35 +71,33 @@ public abstract aspect AbstractInterfaceDrivenDependencyInjectionAspect extends * Select initialization join point as object construction */ public pointcut beanConstruction(Object bean) : - initialization(ConfigurableObject+.new(..)) && this(bean); + initialization(ConfigurableObject+.new(..)) && this(bean); /** * Select deserialization join point made available through ITDs for ConfigurableDeserializationSupport */ public pointcut beanDeserialization(Object bean) : - execution(Object ConfigurableDeserializationSupport+.readResolve()) && - this(bean); + execution(Object ConfigurableDeserializationSupport+.readResolve()) && this(bean); public pointcut leastSpecificSuperTypeConstruction() : initialization(ConfigurableObject.new(..)); // Implementation to support re-injecting dependencies once an object is deserialized + /** * Declare any class implementing Serializable and ConfigurableObject as also implementing - * ConfigurableDeserializationSupport. This allows us to introduce the readResolve() + * ConfigurableDeserializationSupport. This allows us to introduce the {@code readResolve()} * method and select it with the beanDeserialization() pointcut. - * *

Here is an improved version that uses the hasmethod() pointcut and lifts * even the minor requirement on user classes: - * - *

declare parents: ConfigurableObject+ Serializable+
-	 *		            && !hasmethod(Object readResolve() throws ObjectStreamException)
-	 *		            implements ConfigurableDeserializationSupport;
+	 * 
+	 * declare parents: ConfigurableObject+ Serializable+
+	 * && !hasmethod(Object readResolve() throws ObjectStreamException)
+	 * implements ConfigurableDeserializationSupport;
 	 * 
*/ - declare parents: - ConfigurableObject+ && Serializable+ implements ConfigurableDeserializationSupport; + declare parents: ConfigurableObject+ && Serializable+ implements ConfigurableDeserializationSupport; /** * A marker interface to which the {@code readResolve()} is introduced. @@ -111,7 +108,6 @@ public abstract aspect AbstractInterfaceDrivenDependencyInjectionAspect extends /** * Introduce the {@code readResolve()} method so that we can advise its * execution to configure the object. - * *

Note if a method with the same signature already exists in a * {@code Serializable} class of ConfigurableObject type, * that implementation will take precedence (a good thing, since we are diff --git a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AnnotationBeanConfigurerAspect.aj b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AnnotationBeanConfigurerAspect.aj index 2f1e91ccd2..81635ccd0e 100644 --- a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AnnotationBeanConfigurerAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AnnotationBeanConfigurerAspect.aj @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author 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,7 @@ package org.springframework.beans.factory.aspectj; import java.io.Serializable; import org.aspectj.lang.annotation.control.CodeGenerationHint; -import org.springframework.beans.BeansException; + import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.DisposableBean; @@ -44,48 +44,47 @@ import org.springframework.beans.factory.wiring.BeanConfigurerSupport; * @see org.springframework.beans.factory.annotation.Configurable * @see org.springframework.beans.factory.annotation.AnnotationBeanWiringInfoResolver */ -public aspect AnnotationBeanConfigurerAspect - extends AbstractInterfaceDrivenDependencyInjectionAspect +public aspect AnnotationBeanConfigurerAspect extends AbstractInterfaceDrivenDependencyInjectionAspect implements BeanFactoryAware, InitializingBean, DisposableBean { private BeanConfigurerSupport beanConfigurerSupport = new BeanConfigurerSupport(); - public pointcut inConfigurableBean() : @this(Configurable); - public pointcut preConstructionConfiguration() : preConstructionConfigurationSupport(*); - - declare parents: @Configurable * implements ConfigurableObject; - - public void configureBean(Object bean) { - beanConfigurerSupport.configureBean(bean); + public void setBeanFactory(BeanFactory beanFactory) { + this.beanConfigurerSupport.setBeanFactory(beanFactory); + this.beanConfigurerSupport.setBeanWiringInfoResolver(new AnnotationBeanWiringInfoResolver()); } - - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - beanConfigurerSupport.setBeanFactory(beanFactory); - beanConfigurerSupport.setBeanWiringInfoResolver(new AnnotationBeanWiringInfoResolver()); + public void afterPropertiesSet() throws Exception { + this.beanConfigurerSupport.afterPropertiesSet(); } - public void afterPropertiesSet() throws Exception { - beanConfigurerSupport.afterPropertiesSet(); + public void configureBean(Object bean) { + this.beanConfigurerSupport.configureBean(bean); } public void destroy() throws Exception { - beanConfigurerSupport.destroy(); + this.beanConfigurerSupport.destroy(); } + public pointcut inConfigurableBean() : @this(Configurable); + + public pointcut preConstructionConfiguration() : preConstructionConfigurationSupport(*); + /* * An intermediary to match preConstructionConfiguration signature (that doesn't expose the annotation object) */ @CodeGenerationHint(ifNameSuffix="bb0") - private pointcut preConstructionConfigurationSupport(Configurable c) : @this(c) && if(c.preConstruction()); + private pointcut preConstructionConfigurationSupport(Configurable c) : @this(c) && if (c.preConstruction()); + + + declare parents: @Configurable * implements ConfigurableObject; /* * This declaration shouldn't be needed, * except for an AspectJ bug (https://bugs.eclipse.org/bugs/show_bug.cgi?id=214559) */ - declare parents: @Configurable Serializable+ - implements ConfigurableDeserializationSupport; + declare parents: @Configurable Serializable+ implements ConfigurableDeserializationSupport; } 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 740ddaddca..696ba51320 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-2012 the original author or authors. + * Copyright 2002-2014 the original author 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 @@ @Configuration public class AspectJCachingConfiguration extends AbstractCachingConfiguration { - @Bean(name=AnnotationConfigUtils.CACHE_ASPECT_BEAN_NAME) + @Bean(name = AnnotationConfigUtils.CACHE_ASPECT_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public AnnotationCacheAspect cacheAspect() { AnnotationCacheAspect cacheAspect = AnnotationCacheAspect.aspectOf(); @@ -47,4 +47,5 @@ public AnnotationCacheAspect cacheAspect() { } return cacheAspect; } + } diff --git a/spring-aspects/src/main/java/org/springframework/context/annotation/aspectj/SpringConfiguredConfiguration.java b/spring-aspects/src/main/java/org/springframework/context/annotation/aspectj/SpringConfiguredConfiguration.java index 8534fd76b5..747f280f27 100644 --- a/spring-aspects/src/main/java/org/springframework/context/annotation/aspectj/SpringConfiguredConfiguration.java +++ b/spring-aspects/src/main/java/org/springframework/context/annotation/aspectj/SpringConfiguredConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 the original author 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,9 +28,9 @@ * annotated with @{@link org.springframework.beans.factory.annotation.Configurable * Configurable}. * - *

This configuration class is automatically imported when using the @{@link - * EnableSpringConfigured} annotation. See {@code @EnableSpringConfigured} Javadoc for - * complete usage details. + *

This configuration class is automatically imported when using the + * @{@link EnableSpringConfigured} annotation. See {@code @EnableSpringConfigured}'s + * javadoc for complete usage details. * * @author Chris Beams * @since 3.1 @@ -42,9 +42,10 @@ public class SpringConfiguredConfiguration { public static final String BEAN_CONFIGURER_ASPECT_BEAN_NAME = "org.springframework.context.config.internalBeanConfigurerAspect"; - @Bean(name=BEAN_CONFIGURER_ASPECT_BEAN_NAME) + @Bean(name = BEAN_CONFIGURER_ASPECT_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public AnnotationBeanConfigurerAspect beanConfigurerAspect() { return AnnotationBeanConfigurerAspect.aspectOf(); } + } diff --git a/spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj b/spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj index 972a324859..4661840f61 100644 --- a/spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj +++ b/spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author 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,16 +18,22 @@ package org.springframework.mock.staticmock; import java.util.Arrays; import java.util.LinkedList; -import java.util.List; + +import org.springframework.util.ObjectUtils; /** * Abstract aspect to enable mocking of methods picked out by a pointcut. - * Sub-aspects must define the mockStaticsTestMethod() pointcut to - * indicate call stacks when mocking should be triggered, and the - * methodToMock() pointcut to pick out a method invocations to mock. + * + *

Sub-aspects must define: + *

    + *
  • the {@link #mockStaticsTestMethod()} pointcut to indicate call stacks + * when mocking should be triggered + *
  • the {@link #methodToMock()} pointcut to pick out method invocations to mock + *
* * @author Rod Johnson * @author Ramnivas Laddad + * @author Sam Brannen */ public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMethod()) { @@ -35,24 +41,34 @@ public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMeth protected abstract pointcut methodToMock(); + private boolean recording = true; - static enum CallResponse { nothing, return_, throw_ }; - // Represents a list of expected calls to static entity methods + static enum CallResponse { + nothing, return_, throw_ + }; + + /** + * Represents a list of expected calls to methods. + */ // Public to allow inserted code to access: is this normal?? public class Expectations { - // Represents an expected call to a static entity method + /** + * Represents an expected call to a method. + */ private class Call { + private final String signature; private final Object[] args; private Object responseObject; // return value or throwable private CallResponse responseType = CallResponse.nothing; - public Call(String name, Object[] args) { - this.signature = name; + + public Call(String signature, Object[] args) { + this.signature = signature; this.args = args; } @@ -77,7 +93,7 @@ public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMeth public Object throwException(String lastSig, Object[] args) { checkSignature(lastSig, args); - throw (RuntimeException)responseObject; + throw (RuntimeException) responseObject; } private void checkSignature(String lastSig, Object[] args) { @@ -88,50 +104,62 @@ public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMeth throw new IllegalArgumentException("Arguments don't match"); } } + + @Override + public String toString() { + return String.format("Call with signature [%s] and arguments %s", this.signature, + ObjectUtils.nullSafeToString(args)); + } } - private List calls = new LinkedList(); - // Calls already verified + /** + * The list of recorded calls. + */ + private final LinkedList calls = new LinkedList(); + + /** + * The number of calls already verified. + */ private int verified; + public void verify() { if (verified != calls.size()) { - throw new IllegalStateException("Expected " + calls.size() - + " calls, received " + verified); + throw new IllegalStateException("Expected " + calls.size() + " calls, but received " + verified); } } /** - * Validate the call and provide the expected return value - * @param lastSig - * @param args - * @return + * Validate the call and provide the expected return value. */ public Object respond(String lastSig, Object[] args) { - Call call = nextCall(); - CallResponse responseType = call.responseType; - if (responseType == CallResponse.return_) { - return call.returnValue(lastSig, args); - } else if(responseType == CallResponse.throw_) { - return (RuntimeException)call.throwException(lastSig, args); - } else if(responseType == CallResponse.nothing) { - // do nothing + Call c = nextCall(); + + switch (c.responseType) { + case return_: { + return c.returnValue(lastSig, args); + } + case throw_: { + return c.throwException(lastSig, args); + } + default: { + throw new IllegalStateException("Behavior of " + c + " not specified"); + } } - throw new IllegalStateException("Behavior of " + call + " not specified"); } private Call nextCall() { - if (verified > calls.size() - 1) { - throw new IllegalStateException("Expected " + calls.size() - + " calls, received " + verified); + verified++; + if (verified > calls.size()) { + throw new IllegalStateException("Expected " + calls.size() + " calls, but received " + verified); } - return calls.get(verified++); + // The 'verified' count is 1-based; whereas, 'calls' is 0-based. + return calls.get(verified - 1); } - public void expectCall(String lastSig, Object lastArgs[]) { - Call call = new Call(lastSig, lastArgs); - calls.add(call); + public void expectCall(String lastSig, Object[] lastArgs) { + calls.add(new Call(lastSig, lastArgs)); } public boolean hasCalls() { @@ -139,29 +167,31 @@ public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMeth } public void expectReturn(Object retVal) { - Call call = calls.get(calls.size() - 1); - if (call.hasResponseSpecified()) { - throw new IllegalStateException("No static method invoked before setting return value"); + Call c = calls.getLast(); + if (c.hasResponseSpecified()) { + throw new IllegalStateException("No method invoked before setting return value"); } - call.setReturnVal(retVal); + c.setReturnVal(retVal); } public void expectThrow(Throwable throwable) { - Call call = calls.get(calls.size() - 1); - if (call.hasResponseSpecified()) { - throw new IllegalStateException("No static method invoked before setting throwable"); + Call c = calls.getLast(); + if (c.hasResponseSpecified()) { + throw new IllegalStateException("No method invoked before setting throwable"); } - call.setThrow(throwable); + c.setThrow(throwable); } } - private Expectations expectations = new Expectations(); + + private final Expectations expectations = new Expectations(); + after() returning : mockStaticsTestMethod() { if (recording && (expectations.hasCalls())) { throw new IllegalStateException( - "Calls recorded, yet playback state never reached: Create expectations then call " - + this.getClass().getSimpleName() + ".playback()"); + "Calls recorded, yet playback state never reached: Create expectations then call " + + this.getClass().getSimpleName() + ".playback()"); } expectations.verify(); } @@ -171,7 +201,8 @@ public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMeth expectations.expectCall(thisJoinPointStaticPart.toLongString(), thisJoinPoint.getArgs()); // Return value doesn't matter return null; - } else { + } + else { return expectations.respond(thisJoinPointStaticPart.toLongString(), thisJoinPoint.getArgs()); } } diff --git a/spring-aspects/src/main/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControl.aj b/spring-aspects/src/main/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControl.aj index 816f6a1e42..a9985e9c7e 100644 --- a/spring-aspects/src/main/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControl.aj +++ b/spring-aspects/src/main/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControl.aj @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author 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,50 +17,69 @@ package org.springframework.mock.staticmock; /** - * Annotation-based aspect to use in test build to enable mocking static methods - * on JPA-annotated {@code @Entity} classes, as used by Roo for finders. + * Annotation-based aspect to use in test builds to enable mocking of static methods + * on JPA-annotated {@code @Entity} classes, as used by Spring Roo for so-called + * finder methods. * - *

Mocking will occur in the call stack of any method in a class (typically a test class) - * that is annotated with the @MockStaticEntityMethods annotation. + *

Mocking will occur within the call stack of any method in a class (typically a + * test class) that is annotated with {@code @MockStaticEntityMethods}. * - *

Also provides static methods to simplify the programming model for - * entering playback mode and setting expected return values. + *

This aspect also provides static methods to simplify the programming model for + * setting expectations and entering playback mode. * *

Usage: *

    - *
  1. Annotate a test class with @MockStaticEntityMethods. - *
  2. In each test method, AnnotationDrivenStaticEntityMockingControl will begin in recording mode. - * Invoke static methods on Entity classes, with each recording-mode invocation - * being followed by an invocation to the static expectReturn() or expectThrow() - * method on AnnotationDrivenStaticEntityMockingControl. - *
  3. Invoke the static AnnotationDrivenStaticEntityMockingControl() method. - *
  4. Call the code you wish to test that uses the static methods. Verification will - * occur automatically. + *
  5. Annotate a test class with {@code @MockStaticEntityMethods}. + *
  6. In each test method, {@code AnnotationDrivenStaticEntityMockingControl} + * will begin in recording mode. + *
  7. Invoke static methods on JPA-annotated {@code @Entity} classes, with each + * recording-mode invocation being followed by an invocation of either the static + * {@link #expectReturn(Object)} method or the static {@link #expectThrow(Throwable)} + * method on {@code AnnotationDrivenStaticEntityMockingControl}. + *
  8. Invoke the static {@link #playback()} method. + *
  9. Call the code you wish to test that uses the static methods. + *
  10. Verification will occur automatically. *
* * @author Rod Johnson * @author Ramnivas Laddad + * @author Sam Brannen * @see MockStaticEntityMethods */ public aspect AnnotationDrivenStaticEntityMockingControl extends AbstractMethodMockingControl { /** - * Stop recording mock calls and enter playback state + * Expect the supplied {@link Object} to be returned by the previous static + * method invocation. + * @see #playback() */ - public static void playback() { - AnnotationDrivenStaticEntityMockingControl.aspectOf().playbackInternal(); - } - public static void expectReturn(Object retVal) { AnnotationDrivenStaticEntityMockingControl.aspectOf().expectReturnInternal(retVal); } + /** + * Expect the supplied {@link Throwable} to be thrown by the previous static + * method invocation. + * @see #playback() + */ public static void expectThrow(Throwable throwable) { AnnotationDrivenStaticEntityMockingControl.aspectOf().expectThrowInternal(throwable); } - // Only matches directly annotated @Test methods, to allow methods in - // @MockStatics classes to invoke each other without resetting the mocking environment + /** + * Stop recording mock expectations and enter playback mode. + * @see #expectReturn(Object) + * @see #expectThrow(Throwable) + */ + public static void playback() { + AnnotationDrivenStaticEntityMockingControl.aspectOf().playbackInternal(); + } + + // Apparently, the following pointcut was originally defined to only match + // methods directly annotated with @Test (in order to allow methods in + // @MockStaticEntityMethods classes to invoke each other without resetting + // the mocking environment); however, this is no longer the case. The current + // pointcut applies to all public methods in @MockStaticEntityMethods classes. protected pointcut mockStaticsTestMethod() : execution(public * (@MockStaticEntityMethods *).*(..)); protected pointcut methodToMock() : execution(public static * (@javax.persistence.Entity *).*(..)); diff --git a/spring-aspects/src/main/java/org/springframework/mock/staticmock/MockStaticEntityMethods.java b/spring-aspects/src/main/java/org/springframework/mock/staticmock/MockStaticEntityMethods.java index 913d147b3e..f68b80640b 100644 --- a/spring-aspects/src/main/java/org/springframework/mock/staticmock/MockStaticEntityMethods.java +++ b/spring-aspects/src/main/java/org/springframework/mock/staticmock/MockStaticEntityMethods.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,11 +22,13 @@ import java.lang.annotation.Target; /** - * Annotation to indicate a test class for whose @Test methods - * static methods on Entity classes should be mocked. See - * {@code AbstractMethodMockingControl}. + * Annotation to indicate a test class for whose {@code @Test} methods + * static methods on JPA-annotated {@code @Entity} classes should be mocked. + * + *

See {@link AnnotationDrivenStaticEntityMockingControl} for details. * * @author Rod Johnson + * @author Sam Brannen */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) diff --git a/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AspectJAsyncConfiguration.java b/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AspectJAsyncConfiguration.java index c2df8e984d..ee02e6624f 100644 --- a/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AspectJAsyncConfiguration.java +++ b/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AspectJAsyncConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,12 +32,12 @@ * @since 3.1 * @see EnableAsync * @see org.springframework.scheduling.annotation.AsyncConfigurationSelector + * @see org.springframework.scheduling.annotation.ProxyAsyncConfiguration */ @Configuration public class AspectJAsyncConfiguration extends AbstractAsyncConfiguration { - @Override - @Bean(name=AnnotationConfigUtils.ASYNC_EXECUTION_ASPECT_BEAN_NAME) + @Bean(name = AnnotationConfigUtils.ASYNC_EXECUTION_ASPECT_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public AnnotationAsyncExecutionAspect asyncAdvisor() { AnnotationAsyncExecutionAspect asyncAspect = AnnotationAsyncExecutionAspect.aspectOf(); diff --git a/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AspectJTransactionManagementConfiguration.java b/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AspectJTransactionManagementConfiguration.java index 582de64462..41993f81d4 100644 --- a/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AspectJTransactionManagementConfiguration.java +++ b/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AspectJTransactionManagementConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,7 +37,7 @@ @Configuration public class AspectJTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration { - @Bean(name=TransactionManagementConfigUtils.TRANSACTION_ASPECT_BEAN_NAME) + @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ASPECT_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public AnnotationTransactionAspect transactionAspect() { AnnotationTransactionAspect txAspect = AnnotationTransactionAspect.aspectOf(); @@ -46,4 +46,5 @@ public AnnotationTransactionAspect transactionAspect() { } return txAspect; } + } diff --git a/spring-aspects/src/test/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControlTest.java b/spring-aspects/src/test/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControlTest.java deleted file mode 100644 index 5a531301c9..0000000000 --- a/spring-aspects/src/test/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControlTest.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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.mock.staticmock; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.springframework.mock.staticmock.AnnotationDrivenStaticEntityMockingControl.expectReturn; -import static org.springframework.mock.staticmock.AnnotationDrivenStaticEntityMockingControl.expectThrow; -import static org.springframework.mock.staticmock.AnnotationDrivenStaticEntityMockingControl.playback; - -import javax.persistence.PersistenceException; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - - -/** - * Test for static entity mocking framework. - * @author Rod Johnson - * @author Ramnivas Laddad - * - */ -@MockStaticEntityMethods -@RunWith(JUnit4.class) -public class AnnotationDrivenStaticEntityMockingControlTest { - - @Test - public void testNoArgIntReturn() { - int expectedCount = 13; - Person.countPeople(); - expectReturn(expectedCount); - playback(); - assertEquals(expectedCount, Person.countPeople()); - } - - @Test(expected=PersistenceException.class) - public void testNoArgThrows() { - Person.countPeople(); - expectThrow(new PersistenceException()); - playback(); - Person.countPeople(); - } - - @Test - public void testArgMethodMatches() { - long id = 13; - Person found = new Person(); - Person.findPerson(id); - expectReturn(found); - playback(); - assertEquals(found, Person.findPerson(id)); - } - - - @Test - public void testLongSeriesOfCalls() { - long id1 = 13; - long id2 = 24; - Person found1 = new Person(); - Person.findPerson(id1); - expectReturn(found1); - Person found2 = new Person(); - Person.findPerson(id2); - expectReturn(found2); - Person.findPerson(id1); - expectReturn(found1); - Person.countPeople(); - expectReturn(0); - playback(); - - assertEquals(found1, Person.findPerson(id1)); - assertEquals(found2, Person.findPerson(id2)); - assertEquals(found1, Person.findPerson(id1)); - assertEquals(0, Person.countPeople()); - } - - // Note delegation is used when tests are invalid and should fail, as otherwise - // the failure will occur on the verify() method in the aspect after - // this method returns, failing the test case - @Test - public void testArgMethodNoMatchExpectReturn() { - try { - new Delegate().testArgMethodNoMatchExpectReturn(); - fail(); - } catch (IllegalArgumentException expected) { - } - } - - @Test(expected=IllegalArgumentException.class) - public void testArgMethodNoMatchExpectThrow() { - new Delegate().testArgMethodNoMatchExpectThrow(); - } - - private void called(Person found, long id) { - assertEquals(found, Person.findPerson(id)); - } - - @Test - public void testReentrant() { - long id = 13; - Person found = new Person(); - Person.findPerson(id); - expectReturn(found); - playback(); - called(found, id); - } - - @Test(expected=IllegalStateException.class) - public void testRejectUnexpectedCall() { - new Delegate().rejectUnexpectedCall(); - } - - @Test(expected=IllegalStateException.class) - public void testFailTooFewCalls() { - new Delegate().failTooFewCalls(); - } - - @Test - public void testEmpty() { - // Test that verification check doesn't blow up if no replay() call happened - } - - @Test(expected=IllegalStateException.class) - public void testDoesntEverReplay() { - new Delegate().doesntEverReplay(); - } - - @Test(expected=IllegalStateException.class) - public void testDoesntEverSetReturn() { - new Delegate().doesntEverSetReturn(); - } -} - diff --git a/spring-aspects/src/test/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControlTests.java b/spring-aspects/src/test/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControlTests.java new file mode 100644 index 0000000000..4993fbc297 --- /dev/null +++ b/spring-aspects/src/test/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControlTests.java @@ -0,0 +1,170 @@ +/* + * Copyright 2002-2014 the original author 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.mock.staticmock; + +import java.rmi.RemoteException; + +import javax.persistence.PersistenceException; + +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.springframework.mock.staticmock.AnnotationDrivenStaticEntityMockingControl.*; + +/** + * Tests for Spring's static entity mocking framework (i.e., @{@link MockStaticEntityMethods} + * and {@link AnnotationDrivenStaticEntityMockingControl}). + * + * @author Rod Johnson + * @author Ramnivas Laddad + * @author Sam Brannen + */ +@MockStaticEntityMethods +public class AnnotationDrivenStaticEntityMockingControlTests { + + @Test + public void noArgumentMethodInvocationReturnsInt() { + int expectedCount = 13; + Person.countPeople(); + expectReturn(expectedCount); + playback(); + assertEquals(expectedCount, Person.countPeople()); + } + + @Test(expected = PersistenceException.class) + public void noArgumentMethodInvocationThrowsException() { + Person.countPeople(); + expectThrow(new PersistenceException()); + playback(); + Person.countPeople(); + } + + @Test + public void methodArgumentsMatch() { + long id = 13; + Person found = new Person(); + Person.findPerson(id); + expectReturn(found); + playback(); + assertEquals(found, Person.findPerson(id)); + } + + @Test + public void longSeriesOfCalls() { + long id1 = 13; + long id2 = 24; + Person found1 = new Person(); + Person.findPerson(id1); + expectReturn(found1); + Person found2 = new Person(); + Person.findPerson(id2); + expectReturn(found2); + Person.findPerson(id1); + expectReturn(found1); + Person.countPeople(); + expectReturn(0); + playback(); + + assertEquals(found1, Person.findPerson(id1)); + assertEquals(found2, Person.findPerson(id2)); + assertEquals(found1, Person.findPerson(id1)); + assertEquals(0, Person.countPeople()); + } + + @Test(expected = IllegalArgumentException.class) + public void methodArgumentsDoNotMatchAndReturnsObject() { + long id = 13; + Person found = new Person(); + Person.findPerson(id); + AnnotationDrivenStaticEntityMockingControl.expectReturn(found); + AnnotationDrivenStaticEntityMockingControl.playback(); + assertEquals(found, Person.findPerson(id + 1)); + } + + @Test(expected = IllegalArgumentException.class) + public void methodArgumentsDoNotMatchAndThrowsException() { + long id = 13; + Person found = new Person(); + Person.findPerson(id); + AnnotationDrivenStaticEntityMockingControl.expectThrow(new PersistenceException()); + AnnotationDrivenStaticEntityMockingControl.playback(); + assertEquals(found, Person.findPerson(id + 1)); + } + + @Test + public void reentrant() { + long id = 13; + Person found = new Person(); + Person.findPerson(id); + expectReturn(found); + playback(); + called(found, id); + } + + private void called(Person found, long id) { + assertEquals(found, Person.findPerson(id)); + } + + @Test(expected = IllegalStateException.class) + public void rejectUnexpectedCall() { + AnnotationDrivenStaticEntityMockingControl.playback(); + Person.countPeople(); + } + + @Test(expected = IllegalStateException.class) + public void tooFewCalls() { + long id = 13; + Person found = new Person(); + Person.findPerson(id); + AnnotationDrivenStaticEntityMockingControl.expectReturn(found); + Person.countPeople(); + AnnotationDrivenStaticEntityMockingControl.expectReturn(25); + AnnotationDrivenStaticEntityMockingControl.playback(); + assertEquals(found, Person.findPerson(id)); + } + + @Test + public void empty() { + // Test that verification check doesn't blow up if no replay() call happened. + } + + @Test(expected = IllegalStateException.class) + public void doesNotEnterPlaybackMode() { + Person.countPeople(); + } + + @Test(expected = IllegalStateException.class) + public void doesNotSetExpectedReturnValue() { + Person.countPeople(); + AnnotationDrivenStaticEntityMockingControl.playback(); + } + + /** + * Note: this test method currently does NOT actually verify that the mock + * verification fails. + */ + // TODO Determine if it's possible for a mock verification failure to fail a test in + // JUnit 4+ if the test method itself throws an expected exception. + @Test(expected = RemoteException.class) + public void verificationFailsEvenWhenTestFailsInExpectedManner() throws Exception { + Person.countPeople(); + AnnotationDrivenStaticEntityMockingControl.playback(); + // No calls in order to allow verification failure + throw new RemoteException(); + } + +} diff --git a/spring-aspects/src/test/java/org/springframework/mock/staticmock/Delegate.java b/spring-aspects/src/test/java/org/springframework/mock/staticmock/Delegate.java deleted file mode 100644 index d4b3206bc3..0000000000 --- a/spring-aspects/src/test/java/org/springframework/mock/staticmock/Delegate.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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.mock.staticmock; - -import static org.junit.Assert.assertEquals; - -import java.rmi.RemoteException; - -import javax.persistence.PersistenceException; - -import org.junit.Ignore; -import org.junit.Test; -import org.springframework.mock.staticmock.AnnotationDrivenStaticEntityMockingControl; -import org.springframework.mock.staticmock.MockStaticEntityMethods; - -//Used because verification failures occur after method returns, -//so we can't test for them in the test case itself -@MockStaticEntityMethods -@Ignore // This isn't meant for direct testing; rather it is driven from AnnotationDrivenStaticEntityMockingControl -public class Delegate { - - @Test - public void testArgMethodNoMatchExpectReturn() { - long id = 13; - Person found = new Person(); - Person.findPerson(id); - AnnotationDrivenStaticEntityMockingControl.expectReturn(found); - AnnotationDrivenStaticEntityMockingControl.playback(); - assertEquals(found, Person.findPerson(id + 1)); - } - - @Test - public void testArgMethodNoMatchExpectThrow() { - long id = 13; - Person found = new Person(); - Person.findPerson(id); - AnnotationDrivenStaticEntityMockingControl.expectThrow(new PersistenceException()); - AnnotationDrivenStaticEntityMockingControl.playback(); - assertEquals(found, Person.findPerson(id + 1)); - } - - @Test - public void failTooFewCalls() { - long id = 13; - Person found = new Person(); - Person.findPerson(id); - AnnotationDrivenStaticEntityMockingControl.expectReturn(found); - Person.countPeople(); - AnnotationDrivenStaticEntityMockingControl.expectReturn(25); - AnnotationDrivenStaticEntityMockingControl.playback(); - assertEquals(found, Person.findPerson(id)); - } - - @Test - public void doesntEverReplay() { - Person.countPeople(); - } - - @Test - public void doesntEverSetReturn() { - Person.countPeople(); - AnnotationDrivenStaticEntityMockingControl.playback(); - } - - @Test - public void rejectUnexpectedCall() { - AnnotationDrivenStaticEntityMockingControl.playback(); - Person.countPeople(); - } - - @Test(expected=RemoteException.class) - public void testVerificationFailsEvenWhenTestFailsInExpectedManner() throws RemoteException { - Person.countPeople(); - AnnotationDrivenStaticEntityMockingControl.playback(); - // No calls to allow verification failure - throw new RemoteException(); - } -} diff --git a/spring-beans/src/main/java/org/springframework/beans/AbstractPropertyAccessor.java b/spring-beans/src/main/java/org/springframework/beans/AbstractPropertyAccessor.java index 258313c90f..58668b8612 100644 --- a/spring-beans/src/main/java/org/springframework/beans/AbstractPropertyAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/AbstractPropertyAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -105,7 +105,7 @@ public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean // Redefined with public visibility. @Override - public Class getPropertyType(String propertyPath) { + public Class getPropertyType(String propertyPath) { return null; } @@ -127,7 +127,7 @@ public Class getPropertyType(String propertyPath) { * @throws InvalidPropertyException if there is no such property or * if the property isn't writable * @throws PropertyAccessException if the property was valid but the - * accessor method failed or a type mismatch occured + * accessor method failed or a type mismatch occurred */ public abstract void setPropertyValue(String propertyName, Object value) throws BeansException; diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanInfoFactory.java b/spring-beans/src/main/java/org/springframework/beans/BeanInfoFactory.java index fe26c29d85..1906fbfdbb 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanInfoFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanInfoFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,33 +20,36 @@ import java.beans.IntrospectionException; /** - * Strategy for creating {@link BeanInfo} instances. + * Strategy interface for creating {@link BeanInfo} instances for Spring beans. + * Can be used to plug in custom bean property resolution strategies (e.g. for other + * languages on the JVM) or more efficient {@link BeanInfo} retrieval algorithms. * *

BeanInfoFactories are are instantiated by the {@link CachedIntrospectionResults}, - * by using the {@link org.springframework.core.io.support.SpringFactoriesLoader} utility - * class. + * by using the {@link org.springframework.core.io.support.SpringFactoriesLoader} + * utility class. * * When a {@link BeanInfo} is to be created, the {@code CachedIntrospectionResults} - * will iterate through the discovered factories, calling {@link - * #getBeanInfo(Class)} on each one. If {@code null} is returned, the next factory will - * be queried. If none of the factories support the class, an standard {@link BeanInfo} - * is created as a default. + * will iterate through the discovered factories, calling {@link #getBeanInfo(Class)} + * on each one. If {@code null} is returned, the next factory will be queried. + * If none of the factories support the class, a standard {@link BeanInfo} will be + * created as a default. * *

Note that the {@link org.springframework.core.io.support.SpringFactoriesLoader} * sorts the {@code BeanInfoFactory} instances by - * {@link org.springframework.core.annotation.Order @Order}, so that ones with - * a higher precedence come first. + * {@link org.springframework.core.annotation.Order @Order}, so that ones with a + * higher precedence come first. * * @author Arjen Poutsma * @since 3.2 + * @see CachedIntrospectionResults + * @see org.springframework.core.io.support.SpringFactoriesLoader */ public interface BeanInfoFactory { /** - * Returns the bean info for the given class, if supported. - * + * Return the bean info for the given class, if supported. * @param beanClass the bean class - * @return the bean info, or {@code null} if not the given class is not supported + * @return the BeanInfo, or {@code null} if the given class is not supported * @throws IntrospectionException in case of exceptions */ BeanInfo getBeanInfo(Class beanClass) throws IntrospectionException; 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 0a605478c1..d18b1ed1ea 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-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,7 +57,7 @@ public abstract class BeanUtils { private static final Log logger = LogFactory.getLog(BeanUtils.class); - // using WeakHashMap as a Set + // Effectively using a WeakHashMap as a Set private static final Map, Boolean> unknownEditorTypes = Collections.synchronizedMap(new WeakHashMap, Boolean>()); @@ -127,7 +127,7 @@ public static T instantiateClass(Class clazz) throws BeanInstantiationExc @SuppressWarnings("unchecked") public static T instantiateClass(Class clazz, Class assignableTo) throws BeanInstantiationException { Assert.isAssignable(assignableTo, clazz); - return (T)instantiateClass(clazz); + return (T) instantiateClass(clazz); } /** @@ -199,7 +199,7 @@ public static Method findMethod(Class clazz, String methodName, Class... p * @return the Method object, or {@code null} if not found * @see Class#getDeclaredMethod */ - public static Method findDeclaredMethod(Class clazz, String methodName, Class[] paramTypes) { + public static Method findDeclaredMethod(Class clazz, String methodName, Class... paramTypes) { try { return clazz.getDeclaredMethod(methodName, paramTypes); } @@ -281,7 +281,7 @@ public static Method findMethodWithMinimalParameters(Method[] methods, String me } else { if (targetMethod.getParameterTypes().length == numParams) { - // Additional candidate with same length. + // Additional candidate with same length numMethodsFoundWithCurrentMinimumArgs++; } } @@ -317,10 +317,8 @@ public static Method findMethodWithMinimalParameters(Method[] methods, String me 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) { throw new IllegalArgumentException("Invalid method signature '" + signature + "': expected closing ')' for args list"); @@ -336,7 +334,7 @@ else if (firstParen == -1 && lastParen == -1) { String methodName = signature.substring(0, firstParen); String[] parameterTypeNames = StringUtils.commaDelimitedListToStringArray(signature.substring(firstParen + 1, lastParen)); - Class[] parameterTypes = new Class[parameterTypeNames.length]; + Class[] parameterTypes = new Class[parameterTypeNames.length]; for (int i = 0; i < parameterTypeNames.length; i++) { String parameterTypeName = parameterTypeNames[i].trim(); try { @@ -381,13 +379,28 @@ public static PropertyDescriptor getPropertyDescriptor(Class clazz, String pr * Find a JavaBeans {@code PropertyDescriptor} for the given method, * with the method either being the read method or the write method for * that bean property. - * @param method the method to find a corresponding PropertyDescriptor for + * @param method the method to find a corresponding PropertyDescriptor for, + * introspecting its declaring class * @return the corresponding PropertyDescriptor, or {@code null} if none * @throws BeansException if PropertyDescriptor lookup fails */ public static PropertyDescriptor findPropertyForMethod(Method method) throws BeansException { + return findPropertyForMethod(method, method.getDeclaringClass()); + } + + /** + * Find a JavaBeans {@code PropertyDescriptor} for the given method, + * with the method either being the read method or the write method for + * that bean property. + * @param method the method to find a corresponding PropertyDescriptor for + * @param clazz the (most specific) class to introspect for descriptors + * @return the corresponding PropertyDescriptor, or {@code null} if none + * @throws BeansException if PropertyDescriptor lookup fails + * @since 3.2.13 + */ + public static PropertyDescriptor findPropertyForMethod(Method method, Class clazz) throws BeansException { Assert.notNull(method, "Method must not be null"); - PropertyDescriptor[] pds = getPropertyDescriptors(method.getDeclaringClass()); + PropertyDescriptor[] pds = getPropertyDescriptors(clazz); for (PropertyDescriptor pd : pds) { if (method.equals(pd.getReadMethod()) || method.equals(pd.getWriteMethod())) { return pd; @@ -455,7 +468,7 @@ public static PropertyEditor findEditorByConvention(Class targetType) { * @param beanClasses the classes to check against * @return the property type, or {@code Object.class} as fallback */ - public static Class findPropertyType(String propertyName, Class[] beanClasses) { + public static Class findPropertyType(String propertyName, Class... beanClasses) { if (beanClasses != null) { for (Class beanClass : beanClasses) { PropertyDescriptor pd = getPropertyDescriptor(beanClass, propertyName); @@ -475,8 +488,7 @@ public static Class findPropertyType(String propertyName, Class[] beanClas */ public static MethodParameter getWriteMethodParameter(PropertyDescriptor pd) { if (pd instanceof GenericTypeAwarePropertyDescriptor) { - return new MethodParameter( - ((GenericTypeAwarePropertyDescriptor) pd).getWriteMethodParameter()); + return new MethodParameter(((GenericTypeAwarePropertyDescriptor) pd).getWriteMethodParameter()); } else { return new MethodParameter(pd.getWriteMethod(), 0); @@ -528,7 +540,7 @@ public static boolean isSimpleValueType(Class clazz) { * @see BeanWrapper */ public static void copyProperties(Object source, Object target) throws BeansException { - copyProperties(source, target, null, null); + copyProperties(source, target, null, (String[]) null); } /** @@ -545,10 +557,8 @@ public static void copyProperties(Object source, Object target) throws BeansExce * @throws BeansException if the copying failed * @see BeanWrapper */ - public static void copyProperties(Object source, Object target, Class editable) - throws BeansException { - - copyProperties(source, target, editable, null); + public static void copyProperties(Object source, Object target, Class editable) throws BeansException { + copyProperties(source, target, editable, (String[]) null); } /** @@ -565,9 +575,7 @@ public static void copyProperties(Object source, Object target, Class editabl * @throws BeansException if the copying failed * @see BeanWrapper */ - public static void copyProperties(Object source, Object target, String[] ignoreProperties) - throws BeansException { - + public static void copyProperties(Object source, Object target, String... ignoreProperties) throws BeansException { copyProperties(source, target, null, ignoreProperties); } @@ -583,7 +591,7 @@ public static void copyProperties(Object source, Object target, String[] ignoreP * @throws BeansException if the copying failed * @see BeanWrapper */ - private static void copyProperties(Object source, Object target, Class editable, String[] ignoreProperties) + private static void copyProperties(Object source, Object target, Class editable, String... ignoreProperties) throws BeansException { Assert.notNull(source, "Source must not be null"); @@ -598,27 +606,30 @@ private static void copyProperties(Object source, Object target, Class editab actualEditable = editable; } PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable); - List ignoreList = (ignoreProperties != null) ? Arrays.asList(ignoreProperties) : null; + List ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null); for (PropertyDescriptor targetPd : targetPds) { - if (targetPd.getWriteMethod() != null && - (ignoreProperties == null || (!ignoreList.contains(targetPd.getName())))) { + Method writeMethod = targetPd.getWriteMethod(); + if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) { PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName()); - if (sourcePd != null && sourcePd.getReadMethod() != null) { - try { - Method readMethod = sourcePd.getReadMethod(); - if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { - readMethod.setAccessible(true); + if (sourcePd != null) { + Method readMethod = sourcePd.getReadMethod(); + if (readMethod != null && + ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) { + try { + if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { + readMethod.setAccessible(true); + } + Object value = readMethod.invoke(source); + if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { + writeMethod.setAccessible(true); + } + writeMethod.invoke(target, value); } - Object value = readMethod.invoke(source); - Method writeMethod = targetPd.getWriteMethod(); - if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { - writeMethod.setAccessible(true); + catch (Throwable ex) { + throw new FatalBeanException( + "Could not copy property '" + targetPd.getName() + "' from source to target", ex); } - writeMethod.invoke(target, value); - } - catch (Throwable ex) { - throw new FatalBeanException("Could not copy properties from source to target", ex); } } } diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanWrapper.java b/spring-beans/src/main/java/org/springframework/beans/BeanWrapper.java index 628ee8e78a..16992e2e16 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanWrapper.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,7 +59,7 @@ public interface BeanWrapper extends ConfigurablePropertyAccessor { * @return the type of the wrapped bean instance, * or {@code null} if no wrapped object has been set */ - Class getWrappedClass(); + Class getWrappedClass(); /** * Obtain the PropertyDescriptors for the wrapped object @@ -79,11 +79,13 @@ public interface BeanWrapper extends ConfigurablePropertyAccessor { PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException; /** - * Set whether this BeanWrapper should attempt to "auto-grow" a nested path that contains a null value. - *

If "true", a null path location will be populated with a default object value and traversed - * instead of resulting in a {@link NullValueInNestedPathException}. Turning this flag on also - * enables auto-growth of collection elements when accessing an out-of-bounds index. - *

Default is "false" on a plain BeanWrapper. + * Set whether this BeanWrapper should attempt to "auto-grow" a + * nested path that contains a {@code null} value. + *

If {@code true}, a {@code null} path location will be populated + * with a default object value and traversed instead of resulting in a + * {@link NullValueInNestedPathException}. Turning this flag on also enables + * auto-growth of collection elements when accessing an out-of-bounds index. + *

Default is {@code false} on a plain BeanWrapper. */ void setAutoGrowNestedPaths(boolean autoGrowNestedPaths); 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 df5def12a8..dd725bf713 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-2012 the original author or authors. + * Copyright 2002-2014 the original author 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 final Object getWrappedInstance() { return this.object; } - public final Class getWrappedClass() { + public final Class getWrappedClass() { return (this.object != null ? this.object.getClass() : null); } @@ -246,7 +246,7 @@ public final Object getRootInstance() { * Return the class of the root object at the top of the path of this BeanWrapper. * @see #getNestedPath */ - public final Class getRootClass() { + public final Class getRootClass() { return (this.rootObject != null ? this.rootObject.getClass() : null); } @@ -304,7 +304,7 @@ public AccessControlContext getSecurityContext() { * Needs to be called when the target object changes. * @param clazz the class to introspect */ - protected void setIntrospectionClass(Class clazz) { + protected void setIntrospectionClass(Class clazz) { if (this.cachedIntrospectionResults != null && !clazz.equals(this.cachedIntrospectionResults.getBeanClass())) { this.cachedIntrospectionResults = null; @@ -352,7 +352,7 @@ protected PropertyDescriptor getPropertyDescriptorInternal(String propertyName) } @Override - public Class getPropertyType(String propertyName) throws BeansException { + public Class getPropertyType(String propertyName) throws BeansException { try { PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName); if (pd != null) { @@ -366,7 +366,7 @@ public Class getPropertyType(String propertyName) throws BeansException { } // Check to see if there is a custom editor, // which might give an indication on the desired target type. - Class editorType = guessPropertyTypeFromEditors(propertyName); + Class editorType = guessPropertyTypeFromEditors(propertyName); if (editorType != null) { return editorType; } @@ -389,7 +389,8 @@ public TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws Bean if (pd.getReadMethod() != null || pd.getWriteMethod() != null) { return TypeDescriptor.nested(property(pd), tokens.keys.length); } - } else { + } + else { if (pd.getReadMethod() != null || pd.getWriteMethod() != null) { return new TypeDescriptor(property(pd)); } @@ -485,13 +486,13 @@ public Object convertForProperty(Object value, String propertyName) throws TypeM throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, "No property '" + propertyName + "' found"); } - return convertForProperty(propertyName, null, value, pd); + return convertForProperty(propertyName, null, value, new TypeDescriptor(property(pd))); } - private Object convertForProperty(String propertyName, Object oldValue, Object newValue, PropertyDescriptor pd) + private Object convertForProperty(String propertyName, Object oldValue, Object newValue, TypeDescriptor td) throws TypeMismatchException { - return convertIfNecessary(propertyName, oldValue, newValue, pd.getPropertyType(), new TypeDescriptor(property(pd))); + return convertIfNecessary(propertyName, oldValue, newValue, td.getType(), td); } private Property property(PropertyDescriptor pd) { @@ -699,7 +700,8 @@ public Object getPropertyValue(String propertyName) throws BeansException { return nestedBw.getPropertyValue(tokens); } - private Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException { + @SuppressWarnings("unchecked") + private Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException { String propertyName = tokens.canonicalName; String actualName = tokens.actualName; PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); @@ -766,20 +768,20 @@ else if (value.getClass().isArray()) { } else if (value instanceof List) { int index = Integer.parseInt(key); - List list = (List) value; + List list = (List) value; growCollectionIfNecessary(list, index, indexedPropertyName, pd, i + 1); value = list.get(index); } else if (value instanceof Set) { // Apply index to Iterator in case of a Set. - Set set = (Set) value; + Set set = (Set) value; int index = Integer.parseInt(key); if (index < 0 || index >= set.size()) { throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, "Cannot get element with index " + index + " from Set of size " + set.size() + ", accessed using property path '" + propertyName + "'"); } - Iterator it = set.iterator(); + Iterator it = set.iterator(); for (int j = 0; it.hasNext(); j++) { Object elem = it.next(); if (j == index) { @@ -789,11 +791,12 @@ else if (value instanceof Set) { } } else if (value instanceof Map) { - Map map = (Map) value; + Map map = (Map) value; Class mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType(pd.getReadMethod(), i + 1); // 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 = mapKeyType != null ? TypeDescriptor.valueOf(mapKeyType) : TypeDescriptor.valueOf(Object.class); + TypeDescriptor typeDescriptor = (mapKeyType != null ? + TypeDescriptor.valueOf(mapKeyType) : TypeDescriptor.valueOf(Object.class)); Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor); value = map.get(convertedMapKey); } @@ -850,16 +853,15 @@ private Object growArrayIfNecessary(Object array, int index, String name) { } } - @SuppressWarnings("unchecked") - private void growCollectionIfNecessary( - Collection collection, int index, String name, PropertyDescriptor pd, int nestingLevel) { + private void growCollectionIfNecessary(Collection collection, int index, String name, + PropertyDescriptor pd, int nestingLevel) { if (!this.autoGrowNestedPaths) { return; } int size = collection.size(); if (index >= size && index < this.autoGrowCollectionLimit) { - Class elementType = GenericCollectionTypeResolver.getCollectionReturnType(pd.getReadMethod(), nestingLevel); + Class elementType = GenericCollectionTypeResolver.getCollectionReturnType(pd.getReadMethod(), nestingLevel); if (elementType != null) { for (int i = collection.size(); i < index + 1; i++) { collection.add(newValue(elementType, name)); @@ -945,7 +947,7 @@ private void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) thro } if (propValue.getClass().isArray()) { PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); - Class requiredType = propValue.getClass().getComponentType(); + Class requiredType = propValue.getClass().getComponentType(); int arrayIndex = Integer.parseInt(key); Object oldValue = null; try { @@ -963,9 +965,9 @@ private void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) thro } else if (propValue instanceof List) { PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); - Class requiredType = GenericCollectionTypeResolver.getCollectionReturnType( + Class requiredType = GenericCollectionTypeResolver.getCollectionReturnType( pd.getReadMethod(), tokens.keys.length); - List list = (List) propValue; + List list = (List) propValue; int index = Integer.parseInt(key); Object oldValue = null; if (isExtractOldValueForEditor() && index < list.size()) { @@ -1000,11 +1002,11 @@ else if (propValue instanceof List) { } else if (propValue instanceof Map) { PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); - Class mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType( + Class mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType( pd.getReadMethod(), tokens.keys.length); - Class mapValueType = GenericCollectionTypeResolver.getMapValueReturnType( + Class mapValueType = GenericCollectionTypeResolver.getMapValueReturnType( pd.getReadMethod(), tokens.keys.length); - Map map = (Map) propValue; + 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 = (mapKeyType != null ? @@ -1023,7 +1025,7 @@ else if (propValue instanceof Map) { 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 [" + pv.getValue() + "]"); + "' is neither an array nor a List nor a Map; returned value was [" + propValue + "]"); } } @@ -1094,7 +1096,8 @@ public Object run() throws Exception { } } } - valueToApply = convertForProperty(propertyName, oldValue, originalValue, pd); + valueToApply = convertForProperty( + propertyName, oldValue, originalValue, new TypeDescriptor(property(pd))); } pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue); } 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 7c8cd51cec..1d4e881e34 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-2013 the original author or authors. + * Copyright 2002-2014 the original author 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 java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.ref.Reference; -import java.lang.ref.WeakReference; +import java.lang.ref.SoftReference; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; @@ -33,6 +33,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.core.SpringProperties; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -50,6 +51,18 @@ * implements the factory design pattern, using a private constructor and * a static {@link #forClass(Class)} factory method to obtain instances. * + *

Note that for caching to work effectively, some preconditions need to be met: + * Prefer an arrangement where the Spring jars live in the same ClassLoader as the + * application classes, which allows for clean caching along with the application's + * lifecycle in any case. For a web application, consider declaring a local + * {@link org.springframework.web.util.IntrospectorCleanupListener} in {@code web.xml} + * in case of a multi-ClassLoader layout, which will allow for effective caching as well. + * + *

In case of a non-clean ClassLoader arrangement without a cleanup listener having + * been set up, this class will fall back to a weak-reference-based caching model that + * recreates much-requested entries every time the garbage collector removed them. In + * such a scenario, consider the {@link #IGNORE_BEANINFO_PROPERTY_NAME} system property. + * * @author Rod Johnson * @author Juergen Hoeller * @since 05 May 2001 @@ -59,11 +72,34 @@ */ public class CachedIntrospectionResults { - private static final Log logger = LogFactory.getLog(CachedIntrospectionResults.class); + /** + * System property that instructs Spring to use the {@link Introspector#IGNORE_ALL_BEANINFO} + * mode when calling the JavaBeans {@link Introspector}: "spring.beaninfo.ignore", with a + * value of "true" skipping the search for {@code BeanInfo} classes (typically for scenarios + * where no such classes are being defined for beans in the application in the first place). + *

The default is "false", considering all {@code BeanInfo} metadata classes, like for + * standard {@link Introspector#getBeanInfo(Class)} calls. Consider switching this flag to + * "true" if you experience repeated ClassLoader access for non-existing {@code BeanInfo} + * classes, in case such access is expensive on startup or on lazy loading. + *

Note that such an effect may also indicate a scenario where caching doesn't work + * effectively: Prefer an arrangement where the Spring jars live in the same ClassLoader + * as the application classes, which allows for clean caching along with the application's + * lifecycle in any case. For a web application, consider declaring a local + * {@link org.springframework.web.util.IntrospectorCleanupListener} in {@code web.xml} + * in case of a multi-ClassLoader layout, which will allow for effective caching as well. + * @see Introspector#getBeanInfo(Class, int) + */ + public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore"; + + + private static final boolean shouldIntrospectorIgnoreBeaninfoClasses = + SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME); /** Stores the BeanInfoFactory instances */ - private static List beanInfoFactories = - SpringFactoriesLoader.loadFactories(BeanInfoFactory.class, CachedIntrospectionResults.class.getClassLoader()); + private static List beanInfoFactories = SpringFactoriesLoader.loadFactories( + BeanInfoFactory.class, CachedIntrospectionResults.class.getClassLoader()); + + private static final Log logger = LogFactory.getLog(CachedIntrospectionResults.class); /** * Set of ClassLoaders that this CachedIntrospectionResults class will always @@ -76,7 +112,7 @@ public class CachedIntrospectionResults { * Needs to be a WeakHashMap with WeakReferences as values to allow * for proper garbage collection in case of multiple class loaders. */ - static final Map classCache = new WeakHashMap(); + static final Map, Object> classCache = new WeakHashMap, Object>(); /** @@ -101,15 +137,14 @@ public static void acceptClassLoader(ClassLoader classLoader) { /** * Clear the introspection cache for the given ClassLoader, removing the - * introspection results for all classes underneath that ClassLoader, - * and deregistering the ClassLoader (and any of its children) from the - * acceptance list. + * introspection results for all classes underneath that ClassLoader, and + * removing the ClassLoader (and its children) from the acceptance list. * @param classLoader the ClassLoader to clear the cache for */ public static void clearClassLoader(ClassLoader classLoader) { synchronized (classCache) { - for (Iterator it = classCache.keySet().iterator(); it.hasNext();) { - Class beanClass = it.next(); + for (Iterator> it = classCache.keySet().iterator(); it.hasNext();) { + Class beanClass = it.next(); if (isUnderneathClassLoader(beanClass.getClassLoader(), classLoader)) { it.remove(); } @@ -127,21 +162,20 @@ public static void clearClassLoader(ClassLoader classLoader) { /** * Create CachedIntrospectionResults for the given bean class. - *

We don't want to use synchronization here. Object references are atomic, - * so we can live with doing the occasional unnecessary lookup at startup only. * @param beanClass the bean class to analyze * @return the corresponding CachedIntrospectionResults * @throws BeansException in case of introspection failure */ - static CachedIntrospectionResults forClass(Class beanClass) throws BeansException { + @SuppressWarnings("unchecked") + static CachedIntrospectionResults forClass(Class beanClass) throws BeansException { CachedIntrospectionResults results; Object value; synchronized (classCache) { value = classCache.get(beanClass); } if (value instanceof Reference) { - Reference ref = (Reference) value; - results = (CachedIntrospectionResults) ref.get(); + Reference ref = (Reference) value; + results = ref.get(); } else { results = (CachedIntrospectionResults) value; @@ -160,7 +194,7 @@ static CachedIntrospectionResults forClass(Class beanClass) throws BeansExceptio } results = new CachedIntrospectionResults(beanClass); synchronized (classCache) { - classCache.put(beanClass, new WeakReference(results)); + classCache.put(beanClass, new SoftReference(results)); } } } @@ -181,8 +215,8 @@ private static boolean isClassLoaderAccepted(ClassLoader classLoader) { synchronized (acceptedClassLoaders) { acceptedLoaderArray = acceptedClassLoaders.toArray(new ClassLoader[acceptedClassLoaders.size()]); } - for (ClassLoader registeredLoader : acceptedLoaderArray) { - if (isUnderneathClassLoader(classLoader, registeredLoader)) { + for (ClassLoader acceptedLoader : acceptedLoaderArray) { + if (isUnderneathClassLoader(classLoader, acceptedLoader)) { return true; } } @@ -225,7 +259,7 @@ private static boolean isUnderneathClassLoader(ClassLoader candidate, ClassLoade * @param beanClass the bean class to analyze * @throws BeansException in case of introspection failure */ - private CachedIntrospectionResults(Class beanClass) throws BeansException { + private CachedIntrospectionResults(Class beanClass) throws BeansException { try { if (logger.isTraceEnabled()) { logger.trace("Getting BeanInfo for class [" + beanClass.getName() + "]"); @@ -240,20 +274,25 @@ private CachedIntrospectionResults(Class beanClass) throws BeansException { } if (beanInfo == null) { // If none of the factories supported the class, fall back to the default - beanInfo = Introspector.getBeanInfo(beanClass); + beanInfo = (shouldIntrospectorIgnoreBeaninfoClasses ? + Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) : + Introspector.getBeanInfo(beanClass)); } this.beanInfo = beanInfo; - // Immediately remove class from Introspector cache, to allow for proper - // garbage collection on class loader shutdown - we cache it here anyway, - // in a GC-friendly manner. In contrast to CachedIntrospectionResults, - // Introspector does not use WeakReferences as values of its WeakHashMap! - Class classToFlush = beanClass; - do { - Introspector.flushFromCaches(classToFlush); - classToFlush = classToFlush.getSuperclass(); + // Only bother with flushFromCaches if the Introspector actually cached... + if (!shouldIntrospectorIgnoreBeaninfoClasses) { + // Immediately remove class from Introspector cache, to allow for proper + // garbage collection on class loader shutdown - we cache it here anyway, + // in a GC-friendly manner. In contrast to CachedIntrospectionResults, + // Introspector does not use WeakReferences as values of its WeakHashMap! + Class classToFlush = beanClass; + do { + Introspector.flushFromCaches(classToFlush); + classToFlush = classToFlush.getSuperclass(); + } + while (classToFlush != null && classToFlush != Object.class); } - while (classToFlush != null); if (logger.isTraceEnabled()) { logger.trace("Caching PropertyDescriptors for class [" + beanClass.getName() + "]"); @@ -263,8 +302,9 @@ private CachedIntrospectionResults(Class beanClass) throws BeansException { // This call is slow so we do it once. PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors(); for (PropertyDescriptor pd : pds) { - if (Class.class.equals(beanClass) && "classLoader".equals(pd.getName())) { - // Ignore Class.getClassLoader() method - nobody needs to bind to that + if (Class.class.equals(beanClass) && + ("classLoader".equals(pd.getName()) || "protectionDomain".equals(pd.getName()))) { + // Ignore Class.getClassLoader() and getProtectionDomain() methods - nobody needs to bind to those continue; } if (logger.isTraceEnabled()) { @@ -286,7 +326,7 @@ BeanInfo getBeanInfo() { return this.beanInfo; } - Class getBeanClass() { + Class getBeanClass() { return this.beanInfo.getBeanDescriptor().getBeanClass(); } @@ -314,7 +354,7 @@ PropertyDescriptor[] getPropertyDescriptors() { return pds; } - private PropertyDescriptor buildGenericTypeAwarePropertyDescriptor(Class beanClass, PropertyDescriptor pd) { + private PropertyDescriptor buildGenericTypeAwarePropertyDescriptor(Class beanClass, PropertyDescriptor pd) { try { return new GenericTypeAwarePropertyDescriptor(beanClass, pd.getName(), pd.getReadMethod(), pd.getWriteMethod(), pd.getPropertyEditorClass()); 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 6606741ee4..022fff3ae2 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-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package org.springframework.beans; import java.awt.Image; - import java.beans.BeanDescriptor; import java.beans.BeanInfo; import java.beans.EventSetDescriptor; @@ -26,10 +25,8 @@ import java.beans.Introspector; import java.beans.MethodDescriptor; import java.beans.PropertyDescriptor; - import java.lang.reflect.Method; import java.lang.reflect.Modifier; - import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -38,13 +35,16 @@ import java.util.Set; import java.util.TreeSet; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import static org.springframework.beans.PropertyDescriptorUtils.*; /** * 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: - *

{@code
+ * 
  * public class Bean {
  *     private Foo foo;
  *
@@ -56,7 +56,7 @@
  *         this.foo = foo;
  *         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. @@ -76,6 +76,8 @@ */ class ExtendedBeanInfo implements BeanInfo { + private static final Log logger = LogFactory.getLog(ExtendedBeanInfo.class); + private final BeanInfo delegate; private final Set propertyDescriptors = @@ -84,8 +86,8 @@ class ExtendedBeanInfo implements BeanInfo { /** * Wrap the given {@link BeanInfo} instance; copy all its existing property descriptors - * locally, wrapping each in a custom {@link SimpleIndexedPropertyDescriptor indexed} or - * {@link SimpleNonIndexedPropertyDescriptor non-indexed} {@code PropertyDescriptor} + * locally, wrapping each in a custom {@link SimpleIndexedPropertyDescriptor indexed} + * or {@link SimplePropertyDescriptor non-indexed} {@code PropertyDescriptor} * variant that bypasses default JDK weak/soft reference management; then search * through its method descriptors to find any non-void returning write methods and * update or create the corresponding {@link PropertyDescriptor} for each one found. @@ -96,15 +98,32 @@ class ExtendedBeanInfo implements BeanInfo { */ public ExtendedBeanInfo(BeanInfo delegate) throws IntrospectionException { this.delegate = delegate; - for (PropertyDescriptor pd : delegate.getPropertyDescriptors()) { - this.propertyDescriptors.add(pd instanceof IndexedPropertyDescriptor ? - new SimpleIndexedPropertyDescriptor((IndexedPropertyDescriptor) pd) : - new SimpleNonIndexedPropertyDescriptor(pd)); + try { + this.propertyDescriptors.add(pd instanceof IndexedPropertyDescriptor ? + new SimpleIndexedPropertyDescriptor((IndexedPropertyDescriptor) pd) : + new SimplePropertyDescriptor(pd)); + } + catch (IntrospectionException ex) { + // Probably simply a method that wasn't meant to follow the JavaBeans pattern... + if (logger.isDebugEnabled()) { + logger.debug("Ignoring invalid bean property '" + pd.getName() + "': " + ex.getMessage()); + } + } } - - for (Method method : findCandidateWriteMethods(delegate.getMethodDescriptors())) { - handleCandidateWriteMethod(method); + MethodDescriptor[] methodDescriptors = delegate.getMethodDescriptors(); + if (methodDescriptors != null) { + for (Method method : findCandidateWriteMethods(methodDescriptors)) { + try { + handleCandidateWriteMethod(method); + } + catch (IntrospectionException ex) { + // We're only trying to find candidates, can easily ignore extra ones here... + if (logger.isDebugEnabled()) { + logger.debug("Ignoring candidate write method [" + method + "]: " + ex.getMessage()); + } + } + } } } @@ -117,7 +136,7 @@ private List findCandidateWriteMethods(MethodDescriptor[] methodDescript matches.add(method); } } - // sort non-void returning write methods to guard against the ill effects of + // Sort non-void returning write methods to guard against the ill effects of // non-deterministic sorting of methods returned from Class#getDeclaredMethods // under JDK 7. See http://bugs.sun.com/view_bug.do?bug_id=7023180 Collections.sort(matches, new Comparator() { @@ -132,58 +151,44 @@ public static boolean isCandidateWriteMethod(Method method) { String methodName = method.getName(); Class[] parameterTypes = method.getParameterTypes(); int nParams = parameterTypes.length; - if (methodName.length() > 3 && methodName.startsWith("set") && - Modifier.isPublic(method.getModifiers()) && - ( - !void.class.isAssignableFrom(method.getReturnType()) || - Modifier.isStatic(method.getModifiers()) - ) && - (nParams == 1 || (nParams == 2 && parameterTypes[0].equals(int.class)))) { - return true; - } - return false; + return (methodName.length() > 3 && methodName.startsWith("set") && Modifier.isPublic(method.getModifiers()) && + (!void.class.isAssignableFrom(method.getReturnType()) || Modifier.isStatic(method.getModifiers())) && + (nParams == 1 || (nParams == 2 && parameterTypes[0].equals(int.class)))); } private void handleCandidateWriteMethod(Method method) throws IntrospectionException { int nParams = method.getParameterTypes().length; String propertyName = propertyNameFor(method); - Class propertyType = method.getParameterTypes()[nParams-1]; - PropertyDescriptor existingPD = findExistingPropertyDescriptor(propertyName, propertyType); + Class propertyType = method.getParameterTypes()[nParams - 1]; + PropertyDescriptor existingPd = findExistingPropertyDescriptor(propertyName, propertyType); if (nParams == 1) { - if (existingPD == null) { - this.propertyDescriptors.add( - new SimpleNonIndexedPropertyDescriptor(propertyName, null, method)); + if (existingPd == null) { + this.propertyDescriptors.add(new SimplePropertyDescriptor(propertyName, null, method)); } else { - existingPD.setWriteMethod(method); + existingPd.setWriteMethod(method); } } else if (nParams == 2) { - if (existingPD == null) { + if (existingPd == null) { this.propertyDescriptors.add( - new SimpleIndexedPropertyDescriptor( - propertyName, null, null, null, method)); + new SimpleIndexedPropertyDescriptor(propertyName, null, null, null, method)); } - else if (existingPD instanceof IndexedPropertyDescriptor) { - ((IndexedPropertyDescriptor)existingPD).setIndexedWriteMethod(method); + else if (existingPd instanceof IndexedPropertyDescriptor) { + ((IndexedPropertyDescriptor) existingPd).setIndexedWriteMethod(method); } else { - this.propertyDescriptors.remove(existingPD); - this.propertyDescriptors.add( - new SimpleIndexedPropertyDescriptor( - propertyName, existingPD.getReadMethod(), - existingPD.getWriteMethod(), null, method)); + this.propertyDescriptors.remove(existingPd); + this.propertyDescriptors.add(new SimpleIndexedPropertyDescriptor( + propertyName, existingPd.getReadMethod(), existingPd.getWriteMethod(), null, method)); } } else { - throw new IllegalArgumentException( - "write method must have exactly 1 or 2 parameters: " + method); + throw new IllegalArgumentException("Write method must have exactly 1 or 2 parameters: " + method); } } - private PropertyDescriptor findExistingPropertyDescriptor( - String propertyName, Class propertyType) { - + private PropertyDescriptor findExistingPropertyDescriptor(String propertyName, Class propertyType) { for (PropertyDescriptor pd : this.propertyDescriptors) { final Class candidateType; final String candidateName = pd.getName(); @@ -191,16 +196,14 @@ private PropertyDescriptor findExistingPropertyDescriptor( IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd; candidateType = ipd.getIndexedPropertyType(); if (candidateName.equals(propertyName) && - (candidateType.equals(propertyType) || - candidateType.equals(propertyType.getComponentType()))) { + (candidateType.equals(propertyType) || candidateType.equals(propertyType.getComponentType()))) { return pd; } } else { candidateType = pd.getPropertyType(); if (candidateName.equals(propertyName) && - (candidateType.equals(propertyType) || - propertyType.equals(candidateType.getComponentType()))) { + (candidateType.equals(propertyType) || propertyType.equals(candidateType.getComponentType()))) { return pd; } } @@ -209,8 +212,7 @@ private PropertyDescriptor findExistingPropertyDescriptor( } private String propertyNameFor(Method method) { - return Introspector.decapitalize( - method.getName().substring(3, method.getName().length())); + return Introspector.decapitalize(method.getName().substring(3, method.getName().length())); } @@ -221,65 +223,61 @@ private String propertyNameFor(Method method) { * @see #ExtendedBeanInfo(BeanInfo) */ public PropertyDescriptor[] getPropertyDescriptors() { - return this.propertyDescriptors.toArray( - new PropertyDescriptor[this.propertyDescriptors.size()]); + return this.propertyDescriptors.toArray(new PropertyDescriptor[this.propertyDescriptors.size()]); } public BeanInfo[] getAdditionalBeanInfo() { - return delegate.getAdditionalBeanInfo(); + return this.delegate.getAdditionalBeanInfo(); } public BeanDescriptor getBeanDescriptor() { - return delegate.getBeanDescriptor(); + return this.delegate.getBeanDescriptor(); } public int getDefaultEventIndex() { - return delegate.getDefaultEventIndex(); + return this.delegate.getDefaultEventIndex(); } public int getDefaultPropertyIndex() { - return delegate.getDefaultPropertyIndex(); + return this.delegate.getDefaultPropertyIndex(); } public EventSetDescriptor[] getEventSetDescriptors() { - return delegate.getEventSetDescriptors(); + return this.delegate.getEventSetDescriptors(); } public Image getIcon(int iconKind) { - return delegate.getIcon(iconKind); + return this.delegate.getIcon(iconKind); } public MethodDescriptor[] getMethodDescriptors() { - return delegate.getMethodDescriptors(); + return this.delegate.getMethodDescriptors(); } } -class SimpleNonIndexedPropertyDescriptor extends PropertyDescriptor { +class SimplePropertyDescriptor extends PropertyDescriptor { private Method readMethod; + private Method writeMethod; - private Class propertyType; - private Class propertyEditorClass; + private Class propertyType; - public SimpleNonIndexedPropertyDescriptor(PropertyDescriptor original) - throws IntrospectionException { + private Class propertyEditorClass; + public SimplePropertyDescriptor(PropertyDescriptor original) throws IntrospectionException { this(original.getName(), original.getReadMethod(), original.getWriteMethod()); copyNonMethodProperties(original, this); } - public SimpleNonIndexedPropertyDescriptor(String propertyName, - Method readMethod, Method writeMethod) throws IntrospectionException { - + public SimplePropertyDescriptor(String propertyName, Method readMethod, Method writeMethod) throws IntrospectionException { super(propertyName, null, null); - this.setReadMethod(readMethod); - this.setWriteMethod(writeMethod); + this.readMethod = readMethod; + this.writeMethod = writeMethod; this.propertyType = findPropertyType(readMethod, writeMethod); } - @Override public Method getReadMethod() { return this.readMethod; @@ -305,8 +303,9 @@ public Class getPropertyType() { if (this.propertyType == null) { try { this.propertyType = findPropertyType(this.readMethod, this.writeMethod); - } catch (IntrospectionException ex) { - // ignore, as does PropertyDescriptor#getPropertyType + } + catch (IntrospectionException ex) { + // Ignore, as does PropertyDescriptor#getPropertyType } } return this.propertyType; @@ -322,7 +321,6 @@ public void setPropertyEditorClass(Class propertyEditorClass) { this.propertyEditorClass = propertyEditorClass; } - @Override public boolean equals(Object obj) { return PropertyDescriptorUtils.equals(this, obj); @@ -331,8 +329,7 @@ public boolean equals(Object obj) { @Override public String toString() { return String.format("%s[name=%s, propertyType=%s, readMethod=%s, writeMethod=%s]", - this.getClass().getSimpleName(), this.getName(), this.getPropertyType(), - this.readMethod, this.writeMethod); + getClass().getSimpleName(), getName(), getPropertyType(), this.readMethod, this.writeMethod); } } @@ -340,40 +337,37 @@ public String toString() { class SimpleIndexedPropertyDescriptor extends IndexedPropertyDescriptor { private Method readMethod; + private Method writeMethod; + private Class propertyType; - private Class propertyEditorClass; private Method indexedReadMethod; + private Method indexedWriteMethod; - private Class indexedPropertyType; + private Class indexedPropertyType; - public SimpleIndexedPropertyDescriptor(IndexedPropertyDescriptor original) - throws IntrospectionException { + private Class propertyEditorClass; + public SimpleIndexedPropertyDescriptor(IndexedPropertyDescriptor original) throws IntrospectionException { this(original.getName(), original.getReadMethod(), original.getWriteMethod(), original.getIndexedReadMethod(), original.getIndexedWriteMethod()); copyNonMethodProperties(original, this); } - public SimpleIndexedPropertyDescriptor(String propertyName, - Method readMethod, Method writeMethod, - Method indexedReadMethod, Method indexedWriteMethod) - throws IntrospectionException { + public SimpleIndexedPropertyDescriptor(String propertyName, Method readMethod, Method writeMethod, + Method indexedReadMethod, Method indexedWriteMethod) throws IntrospectionException { super(propertyName, null, null, null, null); - this.setReadMethod(readMethod); - this.setWriteMethod(writeMethod); + this.readMethod = readMethod; + this.writeMethod = writeMethod; this.propertyType = findPropertyType(readMethod, writeMethod); - - this.setIndexedReadMethod(indexedReadMethod); - this.setIndexedWriteMethod(indexedWriteMethod); - this.indexedPropertyType = findIndexedPropertyType( - this.getName(), this.propertyType, indexedReadMethod, indexedWriteMethod); + this.indexedReadMethod = indexedReadMethod; + this.indexedWriteMethod = indexedWriteMethod; + this.indexedPropertyType = findIndexedPropertyType(propertyName, this.propertyType, indexedReadMethod, indexedWriteMethod); } - @Override public Method getReadMethod() { return this.readMethod; @@ -399,8 +393,9 @@ public Class getPropertyType() { if (this.propertyType == null) { try { this.propertyType = findPropertyType(this.readMethod, this.writeMethod); - } catch (IntrospectionException ex) { - // ignore, as does IndexedPropertyDescriptor#getPropertyType + } + catch (IntrospectionException ex) { + // Ignore, as does IndexedPropertyDescriptor#getPropertyType } } return this.propertyType; @@ -431,10 +426,10 @@ public Class getIndexedPropertyType() { if (this.indexedPropertyType == null) { try { this.indexedPropertyType = findIndexedPropertyType( - this.getName(), this.getPropertyType(), - this.indexedReadMethod, this.indexedWriteMethod); - } catch (IntrospectionException ex) { - // ignore, as does IndexedPropertyDescriptor#getIndexedPropertyType + getName(), getPropertyType(), this.indexedReadMethod, this.indexedWriteMethod); + } + catch (IntrospectionException ex) { + // Ignore, as does IndexedPropertyDescriptor#getIndexedPropertyType } } return this.indexedPropertyType; @@ -450,41 +445,36 @@ public void setPropertyEditorClass(Class propertyEditorClass) { this.propertyEditorClass = propertyEditorClass; } - /* - * @see java.beans.IndexedPropertyDescriptor#equals(java.lang.Object) + * See java.beans.IndexedPropertyDescriptor#equals(java.lang.Object) */ @Override - public boolean equals(Object obj) { - if (this == obj) { + public boolean equals(Object other) { + if (this == other) { return true; } - - if (obj != null && obj instanceof IndexedPropertyDescriptor) { - IndexedPropertyDescriptor other = (IndexedPropertyDescriptor) obj; - if (!compareMethods(getIndexedReadMethod(), other.getIndexedReadMethod())) { - return false; - } - - if (!compareMethods(getIndexedWriteMethod(), other.getIndexedWriteMethod())) { - return false; - } - - if (getIndexedPropertyType() != other.getIndexedPropertyType()) { - return false; - } - return PropertyDescriptorUtils.equals(this, obj); + if (!(other instanceof IndexedPropertyDescriptor)) { + return false; } - return false; + IndexedPropertyDescriptor otherPd = (IndexedPropertyDescriptor) other; + if (!compareMethods(getIndexedReadMethod(), otherPd.getIndexedReadMethod())) { + return false; + } + if (!compareMethods(getIndexedWriteMethod(), otherPd.getIndexedWriteMethod())) { + return false; + } + if (getIndexedPropertyType() != otherPd.getIndexedPropertyType()) { + return false; + } + return PropertyDescriptorUtils.equals(this, other); } @Override public String toString() { return String.format("%s[name=%s, propertyType=%s, indexedPropertyType=%s, " + "readMethod=%s, writeMethod=%s, indexedReadMethod=%s, indexedWriteMethod=%s]", - this.getClass().getSimpleName(), this.getName(), this.getPropertyType(), - this.getIndexedPropertyType(), this.readMethod, this.writeMethod, - this.indexedReadMethod, this.indexedWriteMethod); + getClass().getSimpleName(), getName(), getPropertyType(), getIndexedPropertyType(), + this.readMethod, this.writeMethod, this.indexedReadMethod, this.indexedWriteMethod); } } @@ -492,7 +482,7 @@ public String toString() { class PropertyDescriptorUtils { /* - * see java.beans.FeatureDescriptor#FeatureDescriptor(FeatureDescriptor) + * See java.beans.FeatureDescriptor#FeatureDescriptor(FeatureDescriptor) */ public static void copyNonMethodProperties(PropertyDescriptor source, PropertyDescriptor target) throws IntrospectionException { @@ -504,14 +494,14 @@ public static void copyNonMethodProperties(PropertyDescriptor source, PropertyDe target.setShortDescription(source.getShortDescription()); target.setDisplayName(source.getDisplayName()); - // copy all attributes (emulating behavior of private FeatureDescriptor#addTable) + // Copy all attributes (emulating behavior of private FeatureDescriptor#addTable) Enumeration keys = source.attributeNames(); while (keys.hasMoreElements()) { String key = keys.nextElement(); target.setValue(key, source.getValue(key)); } - // see java.beans.PropertyDescriptor#PropertyDescriptor(PropertyDescriptor) + // See java.beans.PropertyDescriptor#PropertyDescriptor(PropertyDescriptor) target.setPropertyEditorClass(source.getPropertyEditorClass()); target.setBound(source.isBound()); target.setConstrained(source.isConstrained()); @@ -520,32 +510,39 @@ public static void copyNonMethodProperties(PropertyDescriptor source, PropertyDe /* * See PropertyDescriptor#findPropertyType */ - public static Class findPropertyType(Method readMethod, Method writeMethod) - throws IntrospectionException { - + public static Class findPropertyType(Method readMethod, Method writeMethod) throws IntrospectionException { Class propertyType = null; - if (readMethod != null) { Class[] params = readMethod.getParameterTypes(); if (params.length != 0) { - throw new IntrospectionException("bad read method arg count: " + readMethod); + throw new IntrospectionException("Bad read method arg count: " + readMethod); } propertyType = readMethod.getReturnType(); if (propertyType == Void.TYPE) { - throw new IntrospectionException("read method " - + readMethod.getName() + " returns void"); + throw new IntrospectionException("Read method returns void: " + readMethod); } } if (writeMethod != null) { Class params[] = writeMethod.getParameterTypes(); if (params.length != 1) { - throw new IntrospectionException("bad write method arg count: " + writeMethod); + throw new IntrospectionException("Bad write method arg count: " + writeMethod); } - if (propertyType != null - && !params[0].isAssignableFrom(propertyType)) { - throw new IntrospectionException("type mismatch between read and write methods"); + if (propertyType != null) { + if (propertyType.isAssignableFrom(params[0])) { + // Write method's property type potentially more specific + propertyType = params[0]; + } + else if (params[0].isAssignableFrom(propertyType)) { + // Proceed with read method's property type + } + else { + throw new IntrospectionException( + "Type mismatch between read and write methods: " + readMethod + " - " + writeMethod); + } + } + else { + propertyType = params[0]; } - propertyType = params[0]; } return propertyType; } @@ -554,48 +551,51 @@ public static Class findPropertyType(Method readMethod, Method writeMethod) * See IndexedPropertyDescriptor#findIndexedPropertyType */ public static Class findIndexedPropertyType(String name, Class propertyType, - Method indexedReadMethod, Method indexedWriteMethod) - throws IntrospectionException { + Method indexedReadMethod, Method indexedWriteMethod) throws IntrospectionException { Class indexedPropertyType = null; - if (indexedReadMethod != null) { Class params[] = indexedReadMethod.getParameterTypes(); if (params.length != 1) { - throw new IntrospectionException( - "bad indexed read method arg count"); + throw new IntrospectionException("Bad indexed read method arg count: " + indexedReadMethod); } if (params[0] != Integer.TYPE) { - throw new IntrospectionException( - "non int index to indexed read method"); + throw new IntrospectionException("Non int index to indexed read method: " + indexedReadMethod); } indexedPropertyType = indexedReadMethod.getReturnType(); if (indexedPropertyType == Void.TYPE) { - throw new IntrospectionException( - "indexed read method returns void"); + throw new IntrospectionException("Indexed read method returns void: " + indexedReadMethod); } } if (indexedWriteMethod != null) { Class params[] = indexedWriteMethod.getParameterTypes(); if (params.length != 2) { - throw new IntrospectionException( - "bad indexed write method arg count"); + throw new IntrospectionException("Bad indexed write method arg count: " + indexedWriteMethod); } if (params[0] != Integer.TYPE) { - throw new IntrospectionException( - "non int index to indexed write method"); + throw new IntrospectionException("Non int index to indexed write method: " + indexedWriteMethod); + } + if (indexedPropertyType != null) { + if (indexedPropertyType.isAssignableFrom(params[1])) { + // Write method's property type potentially more specific + indexedPropertyType = params[1]; + } + else if (params[1].isAssignableFrom(indexedPropertyType)) { + // Proceed with read method's property type + } + else { + throw new IntrospectionException("Type mismatch between indexed read and write methods: " + + indexedReadMethod + " - " + indexedWriteMethod); + } } - if (indexedPropertyType != null && indexedPropertyType != params[1]) { - throw new IntrospectionException( - "type mismatch between indexed read and indexed write methods: " + name); + else { + indexedPropertyType = params[1]; } - indexedPropertyType = params[1]; } - if (propertyType != null - && (!propertyType.isArray() || - propertyType.getComponentType() != indexedPropertyType)) { - throw new IntrospectionException( - "type mismatch between indexed and non-indexed methods: " + name); + if (propertyType != null && (!propertyType.isArray() || + propertyType.getComponentType() != indexedPropertyType)) { + throw new IntrospectionException("Type mismatch between indexed and non-indexed methods: " + + indexedReadMethod + " - " + indexedWriteMethod); } return indexedPropertyType; } @@ -605,42 +605,35 @@ public static Class findIndexedPropertyType(String name, Class propertyTyp * return {@code true} if they are objects are equivalent, i.e. both are {@code * PropertyDescriptor}s whose read method, write method, property types, property * editor and flags are equivalent. - * * @see PropertyDescriptor#equals(Object) */ - public static boolean equals(PropertyDescriptor pd1, Object obj) { - if (pd1 == obj) { + public static boolean equals(PropertyDescriptor pd, Object other) { + if (pd == other) { return true; } - if (obj != null && obj instanceof PropertyDescriptor) { - PropertyDescriptor pd2 = (PropertyDescriptor) obj; - if (!compareMethods(pd1.getReadMethod(), pd2.getReadMethod())) { - return false; - } - - if (!compareMethods(pd1.getWriteMethod(), pd2.getWriteMethod())) { - return false; - } - - if (pd1.getPropertyType() == pd2.getPropertyType() - && pd1.getPropertyEditorClass() == pd2.getPropertyEditorClass() - && pd1.isBound() == pd2.isBound() - && pd1.isConstrained() == pd2.isConstrained()) { - return true; - } + if (!(other instanceof PropertyDescriptor)) { + return false; } - return false; + PropertyDescriptor otherPd = (PropertyDescriptor) other; + if (!compareMethods(pd.getReadMethod(), otherPd.getReadMethod())) { + return false; + } + if (!compareMethods(pd.getWriteMethod(), otherPd.getWriteMethod())) { + return false; + } + return (pd.getPropertyType() == otherPd.getPropertyType() && + pd.getPropertyEditorClass() == otherPd.getPropertyEditorClass() && + pd.isBound() == otherPd.isBound() && pd.isConstrained() == otherPd.isConstrained()); } /* - * see PropertyDescriptor#compareMethods + * See PropertyDescriptor#compareMethods */ public static boolean compareMethods(Method a, Method b) { if ((a == null) != (b == null)) { return false; } - - if (a != null && b != null) { + if (a != null) { if (!a.equals(b)) { return false; } @@ -653,7 +646,6 @@ public static boolean compareMethods(Method a, Method b) { /** * Sorts PropertyDescriptor instances alpha-numerically to emulate the behavior of * {@link java.beans.BeanInfo#getPropertyDescriptors()}. - * * @see ExtendedBeanInfo#propertyDescriptors */ class PropertyDescriptorComparator implements Comparator { diff --git a/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfoFactory.java b/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfoFactory.java index 183ffe4e2a..c0da24a95c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfoFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfoFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,8 +25,8 @@ /** * {@link BeanInfoFactory} implementation that evaluates whether bean classes have - * "non-standard" JavaBeans setter methods and are thus candidates for introspection by - * Spring's {@link ExtendedBeanInfo}. + * "non-standard" JavaBeans setter methods and are thus candidates for introspection + * by Spring's (package-visible) {@code ExtendedBeanInfo} implementation. * *

Ordered at {@link Ordered#LOWEST_PRECEDENCE} to allow other user-defined * {@link BeanInfoFactory} types to take precedence. @@ -34,20 +34,20 @@ * @author Chris Beams * @since 3.2 * @see BeanInfoFactory + * @see CachedIntrospectionResults */ -public class ExtendedBeanInfoFactory implements Ordered, BeanInfoFactory { +public class ExtendedBeanInfoFactory implements BeanInfoFactory, Ordered { /** - * Return a new {@link ExtendedBeanInfo} for the given bean class. + * Return an {@link ExtendedBeanInfo} for the given bean class, if applicable. */ public BeanInfo getBeanInfo(Class beanClass) throws IntrospectionException { - return supports(beanClass) ? - new ExtendedBeanInfo(Introspector.getBeanInfo(beanClass)) : null; + return (supports(beanClass) ? new ExtendedBeanInfo(Introspector.getBeanInfo(beanClass)) : null); } /** - * Return whether the given bean class declares or inherits any non-void returning - * JavaBeans or indexed property setter methods. + * Return whether the given bean class declares or inherits any non-void + * returning bean property or indexed property setter methods. */ private boolean supports(Class beanClass) { for (Method method : beanClass.getMethods()) { diff --git a/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java index 0f0c593162..8200d6f8ce 100644 --- a/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java +++ b/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 the original author 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,37 +31,40 @@ import org.springframework.util.StringUtils; /** - * Extension of the standard JavaBeans PropertyDescriptor class, - * overriding {@code getPropertyType()} such that a generically - * declared type will be resolved against the containing bean class. + * Extension of the standard JavaBeans {@link PropertyDescriptor} class, + * overriding {@code getPropertyType()} such that a generically declared + * type variable will be resolved against the containing bean class. * * @author Juergen Hoeller * @since 2.5.2 */ class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor { - private final Class beanClass; + private final Class beanClass; private final Method readMethod; private final Method writeMethod; - private final Class propertyEditorClass; - private volatile Set ambiguousWriteMethods; - private Class propertyType; - private MethodParameter writeMethodParameter; + private Class propertyType; + + private final Class propertyEditorClass; - public GenericTypeAwarePropertyDescriptor(Class beanClass, String propertyName, - Method readMethod, Method writeMethod, Class propertyEditorClass) + + public GenericTypeAwarePropertyDescriptor(Class beanClass, String propertyName, + Method readMethod, Method writeMethod, Class propertyEditorClass) throws IntrospectionException { super(propertyName, null, null); + + if (beanClass == null) { + throw new IntrospectionException("Bean class must not be null"); + } this.beanClass = beanClass; - this.propertyEditorClass = propertyEditorClass; Method readMethodToUse = BridgeMethodResolver.findBridgedMethod(readMethod); Method writeMethodToUse = BridgeMethodResolver.findBridgedMethod(writeMethod); @@ -69,8 +72,11 @@ public GenericTypeAwarePropertyDescriptor(Class beanClass, String propertyName, // Fallback: Original JavaBeans introspection might not have found matching setter // method due to lack of bridge method resolution, in case of the getter using a // covariant return type whereas the setter is defined for the concrete property type. - writeMethodToUse = ClassUtils.getMethodIfAvailable(this.beanClass, - "set" + StringUtils.capitalize(getName()), readMethodToUse.getReturnType()); + Method candidate = ClassUtils.getMethodIfAvailable( + this.beanClass, "set" + StringUtils.capitalize(getName()), (Class[]) null); + if (candidate != null && candidate.getParameterTypes().length == 1) { + writeMethodToUse = candidate; + } } this.readMethod = readMethodToUse; this.writeMethod = writeMethodToUse; @@ -82,7 +88,8 @@ public GenericTypeAwarePropertyDescriptor(Class beanClass, String propertyName, Set ambiguousCandidates = new HashSet(); for (Method method : beanClass.getMethods()) { if (method.getName().equals(writeMethodToUse.getName()) && - !method.equals(writeMethodToUse) && !method.isBridge()) { + !method.equals(writeMethodToUse) && !method.isBridge() && + method.getParameterTypes().length == writeMethodToUse.getParameterTypes().length) { ambiguousCandidates.add(method); } } @@ -90,8 +97,11 @@ public GenericTypeAwarePropertyDescriptor(Class beanClass, String propertyName, this.ambiguousWriteMethods = ambiguousCandidates; } } + + this.propertyEditorClass = propertyEditorClass; } + public Class getBeanClass() { return this.beanClass; } @@ -117,13 +127,19 @@ public Method getWriteMethodForActualAccess() { return this.writeMethod; } - @Override - public Class getPropertyEditorClass() { - return this.propertyEditorClass; + public synchronized MethodParameter getWriteMethodParameter() { + if (this.writeMethod == null) { + return null; + } + if (this.writeMethodParameter == null) { + this.writeMethodParameter = new MethodParameter(this.writeMethod, 0); + GenericTypeResolver.resolveParameterType(this.writeMethodParameter, this.beanClass); + } + return this.writeMethodParameter; } @Override - public synchronized Class getPropertyType() { + public synchronized Class getPropertyType() { if (this.propertyType == null) { if (this.readMethod != null) { this.propertyType = GenericTypeResolver.resolveReturnType(this.readMethod, this.beanClass); @@ -141,15 +157,9 @@ public synchronized Class getPropertyType() { return this.propertyType; } - public synchronized MethodParameter getWriteMethodParameter() { - if (this.writeMethod == null) { - return null; - } - if (this.writeMethodParameter == null) { - this.writeMethodParameter = new MethodParameter(this.writeMethod, 0); - GenericTypeResolver.resolveParameterType(this.writeMethodParameter, this.beanClass); - } - return this.writeMethodParameter; + @Override + public Class getPropertyEditorClass() { + return this.propertyEditorClass; } } 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 390b3f8a12..c7f8b70dd9 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-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -86,7 +86,7 @@ public MutablePropertyValues(Map original) { // There is no replacement of existing property values. if (original != null) { this.propertyValueList = new ArrayList(original.size()); - for (Map.Entry entry : original.entrySet()) { + for (Map.Entry entry : original.entrySet()) { this.propertyValueList.add(new PropertyValue(entry.getKey().toString(), entry.getValue())); } } @@ -303,6 +303,16 @@ public void registerProcessedProperty(String propertyName) { this.processedProperties.add(propertyName); } + /** + * Clear the "processed" registration of the given property, if any. + * @since 3.2.13 + */ + public void clearProcessedProperty(String propertyName) { + if (this.processedProperties != null) { + this.processedProperties.remove(propertyName); + } + } + /** * Mark this holder as containing converted values only * (i.e. no runtime resolution needed anymore). 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 621bae5325..c7faf35524 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-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. @@ -86,7 +86,7 @@ public interface PropertyAccessor { * @throws PropertyAccessException if the property was valid but the * accessor method failed */ - Class getPropertyType(String propertyName) throws BeansException; + Class getPropertyType(String propertyName) throws BeansException; /** * Return a type descriptor for the specified property: @@ -120,7 +120,7 @@ public interface PropertyAccessor { * @throws InvalidPropertyException if there is no such property or * if the property isn't writable * @throws PropertyAccessException if the property was valid but the - * accessor method failed or a type mismatch occured + * accessor method failed or a type mismatch occurred */ void setPropertyValue(String propertyName, Object value) throws BeansException; @@ -130,7 +130,7 @@ public interface PropertyAccessor { * @throws InvalidPropertyException if there is no such property or * if the property isn't writable * @throws PropertyAccessException if the property was valid but the - * accessor method failed or a type mismatch occured + * accessor method failed or a type mismatch occurred */ void setPropertyValue(PropertyValue pv) throws BeansException; @@ -144,7 +144,7 @@ public interface PropertyAccessor { * @throws InvalidPropertyException if there is no such property or * if the property isn't writable * @throws PropertyBatchUpdateException if one or more PropertyAccessExceptions - * occured for specific properties during the batch update. This exception bundles + * occurred for specific properties during the batch update. This exception bundles * all individual PropertyAccessExceptions. All other properties will have been * successfully updated. */ @@ -164,7 +164,7 @@ public interface PropertyAccessor { * @throws InvalidPropertyException if there is no such property or * if the property isn't writable * @throws PropertyBatchUpdateException if one or more PropertyAccessExceptions - * occured for specific properties during the batch update. This exception bundles + * occurred for specific properties during the batch update. This exception bundles * all individual PropertyAccessExceptions. All other properties will have been * successfully updated. * @see #setPropertyValues(PropertyValues, boolean, boolean) @@ -185,7 +185,7 @@ public interface PropertyAccessor { * @throws InvalidPropertyException if there is no such property or * if the property isn't writable * @throws PropertyBatchUpdateException if one or more PropertyAccessExceptions - * occured for specific properties during the batch update. This exception bundles + * occurred for specific properties during the batch update. This exception bundles * all individual PropertyAccessExceptions. All other properties will have been * successfully updated. * @see #setPropertyValues(PropertyValues, boolean, boolean) @@ -208,7 +208,7 @@ void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown) * @throws InvalidPropertyException if there is no such property or * if the property isn't writable * @throws PropertyBatchUpdateException if one or more PropertyAccessExceptions - * occured for specific properties during the batch update. This exception bundles + * occurred for specific properties during the batch update. This exception bundles * all individual PropertyAccessExceptions. All other properties will have been * successfully updated. */ diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyBatchUpdateException.java b/spring-beans/src/main/java/org/springframework/beans/PropertyBatchUpdateException.java index 1919a47122..bfa54687bb 100644 --- a/spring-beans/src/main/java/org/springframework/beans/PropertyBatchUpdateException.java +++ b/spring-beans/src/main/java/org/springframework/beans/PropertyBatchUpdateException.java @@ -130,7 +130,7 @@ public void printStackTrace(PrintWriter pw) { } @Override - public boolean contains(Class exType) { + public boolean contains(Class exType) { if (exType == null) { return false; } 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 82830a198c..ea860ec161 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-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,7 +49,7 @@ final class PropertyMatches { * @param propertyName the name of the property to find possible matches for * @param beanClass the bean class to search for matches */ - public static PropertyMatches forProperty(String propertyName, Class beanClass) { + public static PropertyMatches forProperty(String propertyName, Class beanClass) { return forProperty(propertyName, beanClass, DEFAULT_MAX_DISTANCE); } @@ -59,7 +59,7 @@ public static PropertyMatches forProperty(String propertyName, Class beanClass) * @param beanClass the bean class to search for matches * @param maxDistance the maximum property distance allowed for matches */ - public static PropertyMatches forProperty(String propertyName, Class beanClass, int maxDistance) { + public static PropertyMatches forProperty(String propertyName, Class beanClass, int maxDistance) { return new PropertyMatches(propertyName, beanClass, maxDistance); } @@ -76,7 +76,7 @@ public static PropertyMatches forProperty(String propertyName, Class beanClass, /** * Create a new PropertyMatches instance for the given property. */ - private PropertyMatches(String propertyName, Class beanClass, int maxDistance) { + private PropertyMatches(String propertyName, Class beanClass, int maxDistance) { this.propertyName = propertyName; this.possibleMatches = calculateMatches(BeanUtils.getPropertyDescriptors(beanClass), maxDistance); } @@ -172,7 +172,8 @@ private int calculateStringDistance(String s1, String s2) { char t_j = s2.charAt(j - 1); if (s_i == t_j) { cost = 0; - } else { + } + else { cost = 1; } d[i][j] = Math.min(Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1), 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 feecaf0e59..a2dbc88f12 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-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. @@ -46,8 +46,6 @@ public class PropertyValue extends BeanMetadataAttributeAccessor implements Seri private final Object value; - private Object source; - private boolean optional = false; private boolean converted = false; @@ -82,13 +80,13 @@ 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; this.resolvedDescriptor = original.resolvedDescriptor; + setSource(original.getSource()); copyAttributesFrom(original); } @@ -102,11 +100,11 @@ 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; this.resolvedDescriptor = original.resolvedDescriptor; + setSource(original); copyAttributesFrom(original); } @@ -135,16 +133,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; } @@ -186,7 +196,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 24bc57e9e3..0807c229b5 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-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -187,6 +187,9 @@ public T convertIfNecessary(String propertyName, Object oldValue, Object new // Try to apply some standard type conversion rules if appropriate. if (convertedValue != null) { + if (Object.class.equals(requiredType)) { + return (T) convertedValue; + } if (requiredType.isArray()) { // Array required -> apply appropriate conversion of elements. if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) { @@ -197,13 +200,13 @@ public T convertIfNecessary(String propertyName, Object oldValue, Object new else if (convertedValue instanceof Collection) { // Convert elements to target type, if determined. convertedValue = convertToTypedCollection( - (Collection) convertedValue, propertyName, requiredType, typeDescriptor); + (Collection) convertedValue, propertyName, requiredType, typeDescriptor); standardConversion = true; } else if (convertedValue instanceof Map) { // Convert keys and values to respective target type, if determined. convertedValue = convertToTypedMap( - (Map) convertedValue, propertyName, requiredType, typeDescriptor); + (Map) convertedValue, propertyName, requiredType, typeDescriptor); standardConversion = true; } if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) { @@ -217,8 +220,8 @@ else if (convertedValue instanceof Map) { else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) { if (firstAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) { try { - Constructor strCtor = requiredType.getConstructor(String.class); - return (T) BeanUtils.instantiateClass(strCtor, convertedValue); + Constructor strCtor = requiredType.getConstructor(String.class); + return BeanUtils.instantiateClass(strCtor, convertedValue); } catch (NoSuchMethodException ex) { // proceed with field lookup @@ -286,19 +289,19 @@ private Object attemptToConvertStringToEnum(Class requiredType, String trimme if (index > - 1) { String enumType = trimmedValue.substring(0, index); String fieldName = trimmedValue.substring(index + 1); - ClassLoader loader = this.targetObject.getClass().getClassLoader(); + ClassLoader cl = this.targetObject.getClass().getClassLoader(); try { - Class enumValueType = loader.loadClass(enumType); + Class enumValueType = ClassUtils.forName(enumType, cl); Field enumField = enumValueType.getField(fieldName); convertedValue = enumField.get(null); } catch (ClassNotFoundException ex) { - if(logger.isTraceEnabled()) { - logger.trace("Enum class [" + enumType + "] cannot be loaded from [" + loader + "]", ex); + if (logger.isTraceEnabled()) { + logger.trace("Enum class [" + enumType + "] cannot be loaded", ex); } } catch (Throwable ex) { - if(logger.isTraceEnabled()) { + if (logger.isTraceEnabled()) { logger.trace("Field [" + fieldName + "] isn't an enum value for type [" + enumType + "]", ex); } } @@ -328,7 +331,7 @@ private Object attemptToConvertStringToEnum(Class requiredType, String trimme * @param requiredType the type to find an editor for * @return the corresponding editor, or {@code null} if none */ - private PropertyEditor findDefaultEditor(Class requiredType) { + private PropertyEditor findDefaultEditor(Class requiredType) { PropertyEditor editor = null; if (requiredType != null) { // No custom editor -> check BeanWrapperImpl's default editors. @@ -456,10 +459,10 @@ private Object doConvertTextValue(Object oldValue, String newTextValue, Property private Object convertToTypedArray(Object input, String propertyName, Class componentType) { if (input instanceof Collection) { // Convert Collection elements to array elements. - Collection coll = (Collection) input; + Collection coll = (Collection) input; Object result = Array.newInstance(componentType, coll.size()); int i = 0; - for (Iterator it = coll.iterator(); it.hasNext(); i++) { + for (Iterator it = coll.iterator(); it.hasNext(); i++) { Object value = convertIfNecessary( buildIndexedPropertyName(propertyName, i), null, it.next(), componentType); Array.set(result, i, value); @@ -492,8 +495,8 @@ else if (input.getClass().isArray()) { } @SuppressWarnings("unchecked") - private Collection convertToTypedCollection( - Collection original, String propertyName, Class requiredType, TypeDescriptor typeDescriptor) { + private Collection convertToTypedCollection( + Collection original, String propertyName, Class requiredType, TypeDescriptor typeDescriptor) { if (!Collection.class.isAssignableFrom(requiredType)) { return original; @@ -516,7 +519,7 @@ private Collection convertToTypedCollection( return original; } - Iterator it; + Iterator it; try { it = original.iterator(); if (it == null) { @@ -535,13 +538,13 @@ private Collection convertToTypedCollection( return original; } - Collection convertedCopy; + Collection convertedCopy; try { if (approximable) { convertedCopy = CollectionFactory.createApproximateCollection(original, original.size()); } else { - convertedCopy = (Collection) requiredType.newInstance(); + convertedCopy = (Collection) requiredType.newInstance(); } } catch (Throwable ex) { @@ -574,8 +577,8 @@ private Collection convertToTypedCollection( } @SuppressWarnings("unchecked") - private Map convertToTypedMap( - Map original, String propertyName, Class requiredType, TypeDescriptor typeDescriptor) { + private Map convertToTypedMap( + Map original, String propertyName, Class requiredType, TypeDescriptor typeDescriptor) { if (!Map.class.isAssignableFrom(requiredType)) { return original; @@ -599,7 +602,7 @@ private Map convertToTypedMap( return original; } - Iterator it; + Iterator it; try { it = original.entrySet().iterator(); if (it == null) { @@ -618,13 +621,13 @@ private Map convertToTypedMap( return original; } - Map convertedCopy; + Map convertedCopy; try { if (approximable) { convertedCopy = CollectionFactory.createApproximateMap(original, original.size()); } else { - convertedCopy = (Map) requiredType.newInstance(); + convertedCopy = (Map) requiredType.newInstance(); } } catch (Throwable ex) { @@ -636,7 +639,7 @@ private Map convertToTypedMap( } while (it.hasNext()) { - Map.Entry entry = (Map.Entry) it.next(); + Map.Entry entry = (Map.Entry) it.next(); Object key = entry.getKey(); Object value = entry.getValue(); String keyedPropertyName = buildKeyedPropertyName(propertyName, key); @@ -671,7 +674,7 @@ private String buildKeyedPropertyName(String propertyName, Object key) { null); } - private boolean canCreateCopy(Class requiredType) { + private boolean canCreateCopy(Class requiredType) { return (!requiredType.isInterface() && !Modifier.isAbstract(requiredType.getModifiers()) && Modifier.isPublic(requiredType.getModifiers()) && ClassUtils.hasConstructor(requiredType)); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanCreationException.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanCreationException.java index 2ffe909a94..7af4087576 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanCreationException.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanCreationException.java @@ -123,7 +123,7 @@ public String getResourceDescription() { /** * Add a related cause to this bean creation exception, - * not being a direct cause of the failure but having occured + * not being a direct cause of the failure but having occurred * earlier in the creation of the same bean instance. * @param ex the related cause to add */ @@ -185,7 +185,7 @@ public void printStackTrace(PrintWriter pw) { } @Override - public boolean contains(Class exClass) { + public boolean contains(Class exClass) { if (super.contains(exClass)) { return true; } 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 8c73a16fd7..d13b514dbb 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-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. @@ -66,24 +66,27 @@ * 1. BeanNameAware's {@code setBeanName}
* 2. BeanClassLoaderAware's {@code setBeanClassLoader}
* 3. BeanFactoryAware's {@code setBeanFactory}
- * 4. ResourceLoaderAware's {@code setResourceLoader} + * 4. EnvironmentAware's {@code setEnvironment} + * 5. EmbeddedValueResolverAware's {@code setEmbeddedValueResolver} + * 6. ResourceLoaderAware's {@code setResourceLoader} * (only applicable when running in an application context)
- * 5. ApplicationEventPublisherAware's {@code setApplicationEventPublisher} + * 7. ApplicationEventPublisherAware's {@code setApplicationEventPublisher} * (only applicable when running in an application context)
- * 6. MessageSourceAware's {@code setMessageSource} + * 8. MessageSourceAware's {@code setMessageSource} * (only applicable when running in an application context)
- * 7. ApplicationContextAware's {@code setApplicationContext} + * 9. ApplicationContextAware's {@code setApplicationContext} * (only applicable when running in an application context)
- * 8. ServletContextAware's {@code setServletContext} + * 10. 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 + * 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 + * 1. {@code postProcessBeforeDestruction} methods of DestructionAwareBeanPostProcessors + * 2. DisposableBean's {@code destroy}
+ * 3. a custom destroy-method definition * * @author Rod Johnson * @author Juergen Hoeller @@ -114,6 +117,7 @@ public interface BeanFactory { */ String FACTORY_BEAN_PREFIX = "&"; + /** * Return an instance, which may be shared or independent, of the specified bean. *

This method allows a Spring BeanFactory to be used as a replacement for the @@ -151,15 +155,16 @@ public interface BeanFactory { /** * 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}. + * @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 */ @@ -170,8 +175,7 @@ public interface BeanFactory { *

Allows for specifying explicit constructor arguments / factory method arguments, * overriding the specified default arguments (if any) in the bean definition. * @param name the name of the bean to retrieve - * @param args arguments to use if creating a prototype using explicit arguments to a - * static factory method. It is invalid to use a non-null args value in any other case. + * @param args arguments to use when creating a prototype using explicit arguments * @return an instance of the bean * @throws NoSuchBeanDefinitionException if there is no such bean definition * @throws BeanDefinitionStoreException if arguments have been given but 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 657bb1ebd1..701b638c60 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-2013 the original author or authors. + * Copyright 2002-2015 the original author 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; - import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; @@ -139,7 +138,7 @@ public static String[] beanNamesIncludingAncestors(ListableBeanFactory lbf) { * @param type the type that beans must match * @return the array of matching bean names, or an empty array if none */ - public static String[] beanNamesForTypeIncludingAncestors(ListableBeanFactory lbf, Class type) { + public static String[] beanNamesForTypeIncludingAncestors(ListableBeanFactory lbf, Class type) { Assert.notNull(lbf, "ListableBeanFactory must not be null"); String[] result = lbf.getBeanNamesForType(type); if (lbf instanceof HierarchicalBeanFactory) { @@ -181,7 +180,7 @@ public static String[] beanNamesForTypeIncludingAncestors(ListableBeanFactory lb * @return the array of matching bean names, or an empty array if none */ public static String[] beanNamesForTypeIncludingAncestors( - ListableBeanFactory lbf, Class type, boolean includeNonSingletons, boolean allowEagerInit) { + ListableBeanFactory lbf, Class type, boolean includeNonSingletons, boolean allowEagerInit) { Assert.notNull(lbf, "ListableBeanFactory must not be null"); String[] result = lbf.getBeanNamesForType(type, includeNonSingletons, allowEagerInit); 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 e1e5ed33f5..9f2453ff65 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 5bd8ea7a9f..106fa0cafc 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-2013 the original author or authors. + * Copyright 2002-2014 the original author 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,21 +20,23 @@ import org.springframework.util.StringUtils; /** - * Exception thrown when a {@code BeanFactory} is asked for a bean instance - * for which it cannot find a definition. + * Exception thrown when a {@code BeanFactory} is asked for a bean instance for which it + * cannot find a definition. This may point to a non-existing bean, a non-unique bean, + * or a manually registered singleton instance without an associated bean definition. * * @author Rod Johnson * @author Juergen Hoeller * @see BeanFactory#getBean(String) * @see BeanFactory#getBean(Class) + * @see NoUniqueBeanDefinitionException */ @SuppressWarnings("serial") public class NoSuchBeanDefinitionException extends BeansException { - /** Name of the missing bean. */ + /** Name of the missing bean */ private String beanName; - /** Required type of the missing bean. */ + /** Required type of the missing bean */ private Class beanType; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/access/el/SpringBeanELResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/access/el/SpringBeanELResolver.java index e97329df74..b2be5132c5 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/access/el/SpringBeanELResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/access/el/SpringBeanELResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,7 +18,6 @@ import java.beans.FeatureDescriptor; import java.util.Iterator; - import javax.el.ELContext; import javax.el.ELException; import javax.el.ELResolver; @@ -78,8 +77,14 @@ public void setValue(ELContext elContext, Object base, Object property, Object v String beanName = property.toString(); BeanFactory bf = getBeanFactory(elContext); if (bf.containsBean(beanName)) { - throw new PropertyNotWritableException( - "Variable '" + beanName + "' refers to a Spring bean which by definition is not writable"); + if (value == bf.getBean(beanName)) { + // Setting the bean reference to the same value is alright - can simply be ignored... + elContext.setPropertyResolved(true); + } + else { + throw new PropertyNotWritableException( + "Variable '" + beanName + "' refers to a Spring bean which by definition is not writable"); + } } } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AnnotatedGenericBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AnnotatedGenericBeanDefinition.java index f0fd4c5cac..53c1011b8c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AnnotatedGenericBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AnnotatedGenericBeanDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,15 +58,19 @@ public AnnotatedGenericBeanDefinition(Class beanClass) { * allowing for ASM-based processing and avoidance of early loading of the bean class. * Note that this constructor is functionally equivalent to * {@link org.springframework.context.annotation.ScannedGenericBeanDefinition - * ScannedGenericBeanDefinition}, however the semantics of the latter indicate that - * a bean was discovered specifically via component-scanning as opposed to other - * means. + * ScannedGenericBeanDefinition}, however the semantics of the latter indicate that a + * bean was discovered specifically via component-scanning as opposed to other means. * @param metadata the annotation metadata for the bean class in question * @since 3.1.1 */ public AnnotatedGenericBeanDefinition(AnnotationMetadata metadata) { Assert.notNull(metadata, "AnnotationMetadata must not be null"); - setBeanClassName(metadata.getClassName()); + if (metadata instanceof StandardAnnotationMetadata) { + setBeanClass(((StandardAnnotationMetadata) metadata).getIntrospectedClass()); + } + else { + setBeanClassName(metadata.getClassName()); + } this.metadata = metadata; } 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 6775dd78ed..0e6db6f36a 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-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,6 +59,7 @@ import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; /** * {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation @@ -121,8 +122,8 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean private final Map, Constructor[]> candidateConstructorsCache = new ConcurrentHashMap, Constructor[]>(64); - private final Map, InjectionMetadata> injectionMetadataCache = - new ConcurrentHashMap, InjectionMetadata>(64); + private final Map injectionMetadataCache = + new ConcurrentHashMap(64); /** @@ -134,9 +135,9 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean public AutowiredAnnotationBeanPostProcessor() { this.autowiredAnnotationTypes.add(Autowired.class); this.autowiredAnnotationTypes.add(Value.class); - ClassLoader cl = AutowiredAnnotationBeanPostProcessor.class.getClassLoader(); try { - this.autowiredAnnotationTypes.add((Class) cl.loadClass("javax.inject.Inject")); + this.autowiredAnnotationTypes.add((Class) + ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader())); logger.info("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring"); } catch (ClassNotFoundException ex) { @@ -214,7 +215,7 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException { public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) { if (beanType != null) { - InjectionMetadata metadata = findAutowiringMetadata(beanType); + InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null); metadata.checkConfigMembers(beanDefinition); } } @@ -232,24 +233,25 @@ public Constructor[] determineCandidateConstructors(Class beanClass, Strin Constructor requiredConstructor = null; Constructor defaultConstructor = null; for (Constructor candidate : rawCandidates) { - Annotation annotation = findAutowiredAnnotation(candidate); - if (annotation != null) { + Annotation ann = findAutowiredAnnotation(candidate); + if (ann != null) { if (requiredConstructor != null) { - throw new BeanCreationException("Invalid autowire-marked constructor: " + candidate + - ". Found another constructor with 'required' Autowired annotation: " + + throw new BeanCreationException(beanName, + "Invalid autowire-marked constructor: " + candidate + + ". 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(annotation); + boolean required = determineRequiredStatus(ann); if (required) { if (!candidates.isEmpty()) { - throw new BeanCreationException( + throw new BeanCreationException(beanName, "Invalid autowire-marked constructors: " + candidates + - ". Found another constructor with 'required' Autowired annotation: " + - requiredConstructor); + ". Found constructor with 'required' Autowired annotation: " + + candidate); } requiredConstructor = candidate; } @@ -261,13 +263,21 @@ else if (candidate.getParameterTypes().length == 0) { } if (!candidates.isEmpty()) { // Add default constructor to list of optional constructors, as fallback. - if (requiredConstructor == null && defaultConstructor != null) { - candidates.add(defaultConstructor); + if (requiredConstructor == null) { + if (defaultConstructor != null) { + candidates.add(defaultConstructor); + } + 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)); + } } - candidateConstructors = candidates.toArray(new Constructor[candidates.size()]); + candidateConstructors = candidates.toArray(new Constructor[candidates.size()]); } else { - candidateConstructors = new Constructor[0]; + candidateConstructors = new Constructor[0]; } this.candidateConstructorsCache.put(beanClass, candidateConstructors); } @@ -280,7 +290,7 @@ else if (candidate.getParameterTypes().length == 0) { public PropertyValues postProcessPropertyValues( PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { - InjectionMetadata metadata = findAutowiringMetadata(bean.getClass()); + InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs); try { metadata.inject(bean, beanName, pvs); } @@ -298,7 +308,7 @@ public PropertyValues postProcessPropertyValues( */ public void processInjection(Object bean) throws BeansException { Class clazz = bean.getClass(); - InjectionMetadata metadata = findAutowiringMetadata(clazz); + InjectionMetadata metadata = findAutowiringMetadata(clazz.getName(), clazz, null); try { metadata.inject(bean, null, null); } @@ -308,15 +318,20 @@ public void processInjection(Object bean) throws BeansException { } - private InjectionMetadata findAutowiringMetadata(Class clazz) { + private InjectionMetadata findAutowiringMetadata(String beanName, Class clazz, PropertyValues pvs) { + // Fall back to class name as cache key, for backwards compatibility with custom callers. + String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName()); // Quick check on the concurrent map first, with minimal locking. - InjectionMetadata metadata = this.injectionMetadataCache.get(clazz); - if (metadata == null) { + InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey); + if (InjectionMetadata.needsRefresh(metadata, clazz)) { synchronized (this.injectionMetadataCache) { - metadata = this.injectionMetadataCache.get(clazz); - if (metadata == null) { + metadata = this.injectionMetadataCache.get(cacheKey); + if (InjectionMetadata.needsRefresh(metadata, clazz)) { + if (metadata != null) { + metadata.clear(pvs); + } metadata = buildAutowiringMetadata(clazz); - this.injectionMetadataCache.put(clazz, metadata); + this.injectionMetadataCache.put(cacheKey, metadata); } } } @@ -330,23 +345,25 @@ private InjectionMetadata buildAutowiringMetadata(Class clazz) { do { LinkedList currElements = new LinkedList(); for (Field field : targetClass.getDeclaredFields()) { - Annotation annotation = findAutowiredAnnotation(field); - if (annotation != null) { + Annotation ann = findAutowiredAnnotation(field); + if (ann != null) { if (Modifier.isStatic(field.getModifiers())) { if (logger.isWarnEnabled()) { logger.warn("Autowired annotation is not supported on static fields: " + field); } continue; } - boolean required = determineRequiredStatus(annotation); + boolean required = determineRequiredStatus(ann); currElements.add(new AutowiredFieldElement(field, required)); } } for (Method method : targetClass.getDeclaredMethods()) { + Annotation ann = null; Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); - Annotation annotation = BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod) ? - findAutowiredAnnotation(bridgedMethod) : findAutowiredAnnotation(method); - if (annotation != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { + if (BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) { + ann = findAutowiredAnnotation(bridgedMethod); + } + if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { if (Modifier.isStatic(method.getModifiers())) { if (logger.isWarnEnabled()) { logger.warn("Autowired annotation is not supported on static methods: " + method); @@ -358,8 +375,8 @@ private InjectionMetadata buildAutowiringMetadata(Class clazz) { logger.warn("Autowired annotation should be used on methods with actual parameters: " + method); } } - boolean required = determineRequiredStatus(annotation); - PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method); + boolean required = determineRequiredStatus(ann); + PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); currElements.add(new AutowiredMethodElement(method, required, pd)); } } @@ -373,9 +390,9 @@ private InjectionMetadata buildAutowiringMetadata(Class clazz) { private Annotation findAutowiredAnnotation(AccessibleObject ao) { for (Class type : this.autowiredAnnotationTypes) { - Annotation annotation = AnnotationUtils.getAnnotation(ao, type); - if (annotation != null) { - return annotation; + Annotation ann = AnnotationUtils.getAnnotation(ao, type); + if (ann != null) { + return ann; } } return null; @@ -400,21 +417,21 @@ protected Map findAutowireCandidates(Class type) throws BeansE *

A 'required' dependency means that autowiring should fail when no beans * are found. Otherwise, the autowiring process will simply bypass the field * or method when no beans are found. - * @param annotation the Autowired annotation + * @param ann the Autowired annotation * @return whether the annotation indicates that a dependency is required */ - protected boolean determineRequiredStatus(Annotation annotation) { + protected boolean determineRequiredStatus(Annotation ann) { try { - Method method = ReflectionUtils.findMethod(annotation.annotationType(), this.requiredParameterName); + Method method = ReflectionUtils.findMethod(ann.annotationType(), this.requiredParameterName); if (method == null) { - // annotations like @Inject and @Value don't have a method (attribute) named "required" + // Annotations like @Inject and @Value don't have a method (attribute) named "required" // -> default to required status return true; } - return (this.requiredParameterValue == (Boolean) ReflectionUtils.invokeMethod(method, annotation)); + return (this.requiredParameterValue == (Boolean) ReflectionUtils.invokeMethod(method, ann)); } catch (Exception ex) { - // an exception was thrown during reflective invocation of the required attribute + // An exception was thrown during reflective invocation of the required attribute // -> default to required status return true; } @@ -480,14 +497,14 @@ protected void inject(Object bean, String beanName, PropertyValues pvs) throws T value = resolvedCachedArgument(beanName, this.cachedFieldValue); } else { - DependencyDescriptor descriptor = new DependencyDescriptor(field, this.required); + DependencyDescriptor desc = new DependencyDescriptor(field, this.required); Set autowiredBeanNames = new LinkedHashSet(1); TypeConverter typeConverter = beanFactory.getTypeConverter(); - value = beanFactory.resolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter); + value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); synchronized (this) { if (!this.cached) { if (value != null || this.required) { - this.cachedFieldValue = descriptor; + this.cachedFieldValue = desc; registerDependentBeans(beanName, autowiredBeanNames); if (autowiredBeanNames.size() == 1) { String autowiredBeanName = autowiredBeanNames.iterator().next(); 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 5b604fd263..eb691a14fd 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-2012 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package org.springframework.beans.factory.annotation; import java.lang.reflect.Method; -import java.util.Map; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryUtils; @@ -34,6 +33,7 @@ * Spring's {@link Qualifier @Qualifier} annotation. * * @author Chris Beams + * @author Juergen Hoeller * @since 3.1.2 * @see BeanFactoryUtils */ @@ -76,23 +76,27 @@ else if (beanFactory.containsBean(qualifier)) { * @throws NoSuchBeanDefinitionException if no matching bean of type {@code T} found */ private static T qualifiedBeanOfType(ConfigurableListableBeanFactory bf, Class beanType, String qualifier) { - Map candidateBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(bf, beanType); - T matchingBean = null; - for (String beanName : candidateBeans.keySet()) { + String[] candidateBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(bf, beanType); + String matchingBean = null; + 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 + "'"); } - matchingBean = candidateBeans.get(beanName); + matchingBean = beanName; } } if (matchingBean != null) { - return matchingBean; + return bf.getBean(matchingBean, beanType); + } + else if (bf.containsBean(qualifier)) { + // Fallback: target bean at least found by bean name - probably a manually registered singleton. + return bf.getBean(qualifier, beanType); } else { throw new NoSuchBeanDefinitionException(qualifier, "No matching " + beanType.getSimpleName() + - " bean found for qualifier '" + qualifier + "' - neither qualifier " + "match nor bean name match!"); + " bean found for qualifier '" + qualifier + "' - neither qualifier match nor bean name match!"); } } @@ -128,7 +132,7 @@ private static boolean isQualifierMatch(String qualifier, String beanName, Confi } } catch (NoSuchBeanDefinitionException ex) { - // ignore - can't compare qualifiers for a manually registered singleton object + // Ignore - can't compare qualifiers for a manually registered singleton object } } return false; 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 fe3e3b15ef..9cb34e62ee 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-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,20 +46,21 @@ */ public class InjectionMetadata { - private final Log logger = LogFactory.getLog(InjectionMetadata.class); + private static final Log logger = LogFactory.getLog(InjectionMetadata.class); - private final Class targetClass; + private final Class targetClass; private final Collection injectedElements; private volatile Set checkedElements; - public InjectionMetadata(Class targetClass, Collection elements) { + public InjectionMetadata(Class targetClass, Collection elements) { this.targetClass = targetClass; this.injectedElements = elements; } + public void checkConfigMembers(RootBeanDefinition beanDefinition) { Set checkedElements = new LinkedHashSet(this.injectedElements.size()); for (InjectedElement element : this.injectedElements) { @@ -82,13 +83,31 @@ public void inject(Object target, String beanName, PropertyValues pvs) throws Th boolean debug = logger.isDebugEnabled(); for (InjectedElement element : elementsToIterate) { if (debug) { - logger.debug("Processing injected method of bean '" + beanName + "': " + element); + logger.debug("Processing injected element of bean '" + beanName + "': " + element); } element.inject(target, beanName, pvs); } } } + /** + * @since 3.2.13 + */ + public void clear(PropertyValues pvs) { + Collection elementsToIterate = + (this.checkedElements != null ? this.checkedElements : this.injectedElements); + if (!elementsToIterate.isEmpty()) { + for (InjectedElement element : elementsToIterate) { + element.clearPropertySkipping(pvs); + } + } + } + + + public static boolean needsRefresh(InjectionMetadata metadata, Class clazz) { + return (metadata == null || !metadata.targetClass.equals(clazz)); + } + public static abstract class InjectedElement { @@ -110,7 +129,7 @@ public final Member getMember() { return this.member; } - protected final Class getResourceType() { + protected final Class getResourceType() { if (this.isField) { return ((Field) this.member).getType(); } @@ -122,16 +141,16 @@ else if (this.pd != null) { } } - protected final void checkResourceType(Class resourceType) { + protected final void checkResourceType(Class resourceType) { if (this.isField) { - Class fieldType = ((Field) this.member).getType(); + Class fieldType = ((Field) this.member).getType(); if (!(resourceType.isAssignableFrom(fieldType) || fieldType.isAssignableFrom(resourceType))) { throw new IllegalStateException("Specified field type [" + fieldType + "] is incompatible with resource type [" + resourceType.getName() + "]"); } } else { - Class paramType = + Class paramType = (this.pd != null ? this.pd.getPropertyType() : ((Method) this.member).getParameterTypes()[0]); if (!(resourceType.isAssignableFrom(paramType) || paramType.isAssignableFrom(resourceType))) { throw new IllegalStateException("Specified parameter type [" + paramType + @@ -165,7 +184,7 @@ protected void inject(Object target, String requestingBeanName, PropertyValues p } /** - * Checks whether this injector's property needs to be skipped due to + * Check whether this injector's property needs to be skipped due to * an explicit property value having been specified. Also marks the * affected property as processed for other processors to ignore it. */ @@ -196,6 +215,20 @@ else if (pvs instanceof MutablePropertyValues) { } } + /** + * @since 3.2.13 + */ + protected void clearPropertySkipping(PropertyValues pvs) { + if (pvs == null) { + return; + } + synchronized (pvs) { + if (Boolean.FALSE.equals(this.skip) && this.pd != null && pvs instanceof MutablePropertyValues) { + ((MutablePropertyValues) pvs).clearProcessedProperty(this.pd.getName()); + } + } + } + /** * Either this or {@link #inject} needs to be overridden. */ 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 657ee22666..5645d40030 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-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,10 @@ import org.springframework.beans.TypeConverter; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.support.AutowireCandidateQualifier; import org.springframework.beans.factory.support.AutowireCandidateResolver; @@ -69,9 +72,9 @@ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCan @SuppressWarnings("unchecked") public QualifierAnnotationAutowireCandidateResolver() { this.qualifierTypes.add(Qualifier.class); - ClassLoader cl = QualifierAnnotationAutowireCandidateResolver.class.getClassLoader(); try { - this.qualifierTypes.add((Class) cl.loadClass("javax.inject.Qualifier")); + this.qualifierTypes.add((Class) ClassUtils.forName("javax.inject.Qualifier", + QualifierAnnotationAutowireCandidateResolver.class.getClassLoader())); } catch (ClassNotFoundException ex) { // JSR-330 API not available - simply skip. @@ -227,21 +230,31 @@ protected boolean checkQualifier( Class type = annotation.annotationType(); RootBeanDefinition bd = (RootBeanDefinition) bdHolder.getBeanDefinition(); + AutowireCandidateQualifier qualifier = bd.getQualifier(type.getName()); if (qualifier == null) { qualifier = bd.getQualifier(ClassUtils.getShortName(type)); } if (qualifier == null) { - Annotation targetAnnotation = null; - if (bd.getResolvedFactoryMethod() != null) { - targetAnnotation = AnnotationUtils.getAnnotation(bd.getResolvedFactoryMethod(), type); + // First, check annotation on factory method, if applicable + Annotation targetAnnotation = getFactoryMethodAnnotation(bd, type); + if (targetAnnotation == null) { + RootBeanDefinition dbd = getResolvedDecoratedDefinition(bd); + if (dbd != null) { + targetAnnotation = getFactoryMethodAnnotation(dbd, type); + } } if (targetAnnotation == null) { - // look for matching annotation on the target class + // Look for matching annotation on the target class if (this.beanFactory != null) { - Class beanType = this.beanFactory.getType(bdHolder.getBeanName()); - if (beanType != null) { - targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(beanType), type); + try { + Class beanType = this.beanFactory.getType(bdHolder.getBeanName()); + if (beanType != null) { + targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(beanType), type); + } + } + catch (NoSuchBeanDefinitionException ex) { + // Not the usual case - simply forget about the type check... } } if (targetAnnotation == null && bd.hasBeanClass()) { @@ -252,30 +265,31 @@ protected boolean checkQualifier( return true; } } + Map attributes = AnnotationUtils.getAnnotationAttributes(annotation); if (attributes.isEmpty() && qualifier == null) { - // if no attributes, the qualifier must be present + // If no attributes, the qualifier must be present return false; } for (Map.Entry entry : attributes.entrySet()) { String attributeName = entry.getKey(); Object expectedValue = entry.getValue(); Object actualValue = null; - // check qualifier first + // Check qualifier first if (qualifier != null) { actualValue = qualifier.getAttribute(attributeName); } if (actualValue == null) { - // fall back on bean definition attribute + // Fall back on bean definition attribute actualValue = bd.getAttribute(attributeName); } if (actualValue == null && attributeName.equals(AutowireCandidateQualifier.VALUE_KEY) && expectedValue instanceof String && bdHolder.matchesName((String) expectedValue)) { - // fall back on bean name (or alias) match + // Fall back on bean name (or alias) match continue; } if (actualValue == null && qualifier != null) { - // fall back on default, but only if the qualifier is present + // Fall back on default, but only if the qualifier is present actualValue = AnnotationUtils.getDefaultValue(annotation, attributeName); } if (actualValue != null) { @@ -288,6 +302,25 @@ protected boolean checkQualifier( return true; } + protected RootBeanDefinition getResolvedDecoratedDefinition(RootBeanDefinition rbd) { + BeanDefinitionHolder decDef = rbd.getDecoratedDefinition(); + if (decDef != null && this.beanFactory instanceof ConfigurableListableBeanFactory) { + ConfigurableListableBeanFactory clbf = (ConfigurableListableBeanFactory) this.beanFactory; + if (clbf.containsBeanDefinition(decDef.getBeanName())) { + BeanDefinition dbd = clbf.getMergedBeanDefinition(decDef.getBeanName()); + if (dbd instanceof RootBeanDefinition) { + return (RootBeanDefinition) dbd; + } + } + } + return null; + } + + protected Annotation getFactoryMethodAnnotation(RootBeanDefinition bd, Class type) { + Method resolvedFactoryMethod = bd.getResolvedFactoryMethod(); + return (resolvedFactoryMethod != null ? AnnotationUtils.getAnnotation(resolvedFactoryMethod, type) : null); + } + /** * Determine whether the given dependency carries a value annotation. 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 5c1ad4b751..7cb1685fed 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-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. @@ -153,7 +153,7 @@ public final T getObject() throws Exception { */ @SuppressWarnings("unchecked") private T getEarlySingletonInstance() throws Exception { - Class[] ifcs = getEarlySingletonInterfaces(); + Class[] ifcs = getEarlySingletonInterfaces(); if (ifcs == null) { throw new FactoryBeanNotInitializedException( getClass().getName() + " does not support circular references"); @@ -201,7 +201,7 @@ public void destroy() throws Exception { *

Invoked on initialization of this FactoryBean in case of * a singleton; else, on each {@link #getObject()} call. * @return the object returned by this factory - * @throws Exception if an exception occured during object creation + * @throws Exception if an exception occurred during object creation * @see #getObject() */ protected abstract T createInstance() throws Exception; @@ -218,9 +218,9 @@ public void destroy() throws Exception { * or {@code null} to indicate a FactoryBeanNotInitializedException * @see org.springframework.beans.factory.FactoryBeanNotInitializedException */ - protected Class[] getEarlySingletonInterfaces() { - Class type = getObjectType(); - return (type != null && type.isInterface() ? new Class[] {type} : null); + protected Class[] getEarlySingletonInterfaces() { + Class type = getObjectType(); + return (type != null && type.isInterface() ? new Class[] {type} : null); } /** 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 e71eb1a899..9cf2e3d5eb 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 @@ -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. @@ -114,9 +114,9 @@ public interface AutowireCapableBeanFactory extends BeanFactory { *

Performs full initialization of the bean, including all applicable * {@link BeanPostProcessor BeanPostProcessors}. *

Note: This is intended for creating a fresh instance, populating annotated - * fields and methods as well as applying all standard bean initialiation callbacks. + * fields and methods as well as applying all standard bean initialization callbacks. * It does not imply traditional by-name or by-type autowiring of properties; - * use {@link #createBean(Class, int, boolean)} for that purposes. + * use {@link #createBean(Class, int, boolean)} for those purposes. * @param beanClass the class of the bean to create * @return the new bean instance * @throws BeansException if instantiation or wiring failed @@ -129,7 +129,7 @@ public interface AutowireCapableBeanFactory extends BeanFactory { *

Note: This is essentially intended for (re-)populating annotated fields and * methods, either for new instances or for deserialized instances. It does * not imply traditional by-name or by-type autowiring of properties; - * use {@link #autowireBeanProperties} for that purposes. + * use {@link #autowireBeanProperties} for those purposes. * @param existingBean the existing bean instance * @throws BeansException if wiring failed */ @@ -185,7 +185,7 @@ public interface AutowireCapableBeanFactory extends BeanFactory { * @see #AUTOWIRE_BY_TYPE * @see #AUTOWIRE_CONSTRUCTOR */ - Object createBean(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException; + Object createBean(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException; /** * Instantiate a new bean instance of the given class with the specified autowire @@ -213,7 +213,7 @@ public interface AutowireCapableBeanFactory extends BeanFactory { * @see #applyBeanPostProcessorsBeforeInitialization * @see #applyBeanPostProcessorsAfterInitialization */ - Object autowire(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException; + Object autowire(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException; /** * Autowire the bean properties of the given bean instance by name or type. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java index 1e59e7d134..5bf2fffcdf 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -146,7 +146,7 @@ public boolean hasIndexedArgumentValue(int index) { * untyped values only) * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getIndexedArgumentValue(int index, Class requiredType) { + public ValueHolder getIndexedArgumentValue(int index, Class requiredType) { return getIndexedArgumentValue(index, requiredType, null); } @@ -159,7 +159,7 @@ public ValueHolder getIndexedArgumentValue(int index, Class requiredType) { * unnamed values only) * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getIndexedArgumentValue(int index, Class requiredType, String requiredName) { + public ValueHolder getIndexedArgumentValue(int index, Class requiredType, String requiredName) { Assert.isTrue(index >= 0, "Index must not be negative"); ValueHolder valueHolder = this.indexedArgumentValues.get(index); if (valueHolder != null && @@ -247,7 +247,7 @@ private void addOrMergeGenericArgumentValue(ValueHolder newValue) { * @param requiredType the type to match * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getGenericArgumentValue(Class requiredType) { + public ValueHolder getGenericArgumentValue(Class requiredType) { return getGenericArgumentValue(requiredType, null, null); } @@ -257,7 +257,7 @@ public ValueHolder getGenericArgumentValue(Class requiredType) { * @param requiredName the name to match * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getGenericArgumentValue(Class requiredType, String requiredName) { + public ValueHolder getGenericArgumentValue(Class requiredType, String requiredName) { return getGenericArgumentValue(requiredType, requiredName, null); } @@ -273,7 +273,7 @@ public ValueHolder getGenericArgumentValue(Class requiredType, String requiredNa * in the current resolution process and should therefore not be returned again * @return the ValueHolder for the argument, or {@code null} if none found */ - public ValueHolder getGenericArgumentValue(Class requiredType, String requiredName, Set usedValueHolders) { + public ValueHolder getGenericArgumentValue(Class requiredType, String requiredName, Set usedValueHolders) { for (ValueHolder valueHolder : this.genericArgumentValues) { if (usedValueHolders != null && usedValueHolders.contains(valueHolder)) { continue; @@ -309,10 +309,10 @@ public List getGenericArgumentValues() { * Look for an argument value that either corresponds to the given index * in the constructor argument list or generically matches by type. * @param index the index in the constructor argument list - * @param requiredType the type to match + * @param requiredType the parameter type to match * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getArgumentValue(int index, Class requiredType) { + public ValueHolder getArgumentValue(int index, Class requiredType) { return getArgumentValue(index, requiredType, null, null); } @@ -320,11 +320,11 @@ public ValueHolder getArgumentValue(int index, Class requiredType) { * Look for an argument value that either corresponds to the given index * in the constructor argument list or generically matches by type. * @param index the index in the constructor argument list - * @param requiredType the type to match - * @param requiredName the name to match + * @param requiredType the parameter type to match + * @param requiredName the parameter name to match * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getArgumentValue(int index, Class requiredType, String requiredName) { + public ValueHolder getArgumentValue(int index, Class requiredType, String requiredName) { return getArgumentValue(index, requiredType, requiredName, null); } @@ -332,15 +332,17 @@ public ValueHolder getArgumentValue(int index, Class requiredType, String requir * Look for an argument value that either corresponds to the given index * in the constructor argument list or generically matches by type. * @param index the index in the constructor argument list - * @param requiredType the type to match (can be {@code null} to find - * an untyped argument value) + * @param requiredType the parameter type to match (can be {@code null} + * to find an untyped argument value) + * @param requiredName the parameter name to match (can be {@code null} + * to find an unnamed argument value) * @param usedValueHolders a Set of ValueHolder objects that have already * been used in the current resolution process and should therefore not * be returned again (allowing to return the next generic argument match * in case of multiple generic argument values of the same type) * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getArgumentValue(int index, Class requiredType, String requiredName, Set usedValueHolders) { + public ValueHolder getArgumentValue(int index, Class requiredType, String requiredName, Set usedValueHolders) { Assert.isTrue(index >= 0, "Index must not be negative"); ValueHolder valueHolder = getIndexedArgumentValue(index, requiredType, requiredName); if (valueHolder == null) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java index 2693f8246c..35bdfd904b 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-2012 the original author or authors. + * Copyright 2002-2013 the original author 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 class DependencyDescriptor implements Serializable { private transient Field field; - private Class declaringClass; + private Class declaringClass; private String methodName; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java index 2f6bd6865a..43e9674a6f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -206,7 +206,7 @@ protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryTo visitor.visitBeanDefinition(bd); } catch (Exception ex) { - throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage()); + throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex); } } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertiesFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertiesFactoryBean.java index 9e0673ccfb..b39e0de0da 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertiesFactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertiesFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 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. @@ -91,7 +91,7 @@ public Class getObjectType() { *

Invoked on initialization of this FactoryBean in case of a * shared singleton; else, on each {@link #getObject()} call. * @return the object returned by this factory - * @throws IOException if an exception occured during properties loading + * @throws IOException if an exception occurred during properties loading * @see #mergeProperties() */ protected Properties createProperties() throws IOException { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java index bf9474138f..b51694ffa5 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import org.springframework.beans.BeansException; import org.springframework.core.Constants; +import org.springframework.core.SpringProperties; +import org.springframework.core.env.AbstractEnvironment; import org.springframework.util.PropertyPlaceholderHelper; import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; import org.springframework.util.StringValueResolver; @@ -82,7 +84,8 @@ public class PropertyPlaceholderConfigurer extends PlaceholderConfigurerSupport private int systemPropertiesMode = SYSTEM_PROPERTIES_MODE_FALLBACK; - private boolean searchSystemEnvironment = true; + private boolean searchSystemEnvironment = + !SpringProperties.getFlag(AbstractEnvironment.IGNORE_GETENV_PROPERTY_NAME); /** @@ -216,8 +219,7 @@ protected void processProperties(ConfigurableListableBeanFactory beanFactoryToPr throws BeansException { StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props); - - this.doProcessProperties(beanFactoryToProcess, valueResolver); + doProcessProperties(beanFactoryToProcess, valueResolver); } /** diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java index e4f27fec8b..40f7e3f24e 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,7 +61,7 @@ public TypedStringValue(String value) { * @param value the String value * @param targetType the type to convert to */ - public TypedStringValue(String value, Class targetType) { + public TypedStringValue(String value, Class targetType) { setValue(value); setTargetType(targetType); } @@ -101,7 +101,7 @@ public String getValue() { * for example in BeanFactoryPostProcessors. * @see PropertyPlaceholderConfigurer */ - public void setTargetType(Class targetType) { + public void setTargetType(Class targetType) { Assert.notNull(targetType, "'targetType' must not be null"); this.targetType = targetType; } @@ -109,7 +109,7 @@ public void setTargetType(Class targetType) { /** * Return the type to convert to. */ - public Class getTargetType() { + public Class getTargetType() { Object targetTypeValue = this.targetType; if (!(targetTypeValue instanceof Class)) { throw new IllegalStateException("Typed String value does not carry a resolved target type"); @@ -153,11 +153,11 @@ public boolean hasTargetType() { * @return the resolved type to convert to * @throws ClassNotFoundException if the type cannot be resolved */ - public Class resolveTargetType(ClassLoader classLoader) throws ClassNotFoundException { + public Class resolveTargetType(ClassLoader classLoader) throws ClassNotFoundException { if (this.targetType == null) { return null; } - Class resolvedClass = ClassUtils.forName(getTargetTypeName(), classLoader); + Class resolvedClass = ClassUtils.forName(getTargetTypeName(), classLoader); this.targetType = resolvedClass; return resolvedClass; } 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 09faf0c3b2..651af574da 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-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. @@ -63,7 +63,7 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; +import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor; @@ -135,21 +135,21 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac * Dependency types to ignore on dependency check and autowire, as Set of * Class objects: for example, String. Default is none. */ - private final Set ignoredDependencyTypes = new HashSet(); + private final Set> ignoredDependencyTypes = new HashSet>(); /** * Dependency interfaces to ignore on dependency check and autowire, as Set of * Class objects. By default, only the BeanFactory interface is ignored. */ - private final Set ignoredDependencyInterfaces = new HashSet(); + private final Set> ignoredDependencyInterfaces = new HashSet>(); /** Cache of unfinished FactoryBean instances: FactoryBean name --> BeanWrapper */ private final Map factoryBeanInstanceCache = new ConcurrentHashMap(16); /** Cache of filtered PropertyDescriptors: bean Class -> PropertyDescriptor array */ - private final Map filteredPropertyDescriptorsCache = - new ConcurrentHashMap(64); + private final Map, PropertyDescriptor[]> filteredPropertyDescriptorsCache = + new ConcurrentHashMap, PropertyDescriptor[]>(64); /** @@ -171,6 +171,7 @@ public AbstractAutowireCapableBeanFactory(BeanFactory parentBeanFactory) { setParentBeanFactory(parentBeanFactory); } + /** * Set the instantiation strategy to use for creating bean instances. * Default is CglibSubclassingInstantiationStrategy. @@ -243,7 +244,7 @@ public void setAllowRawInjectionDespiteWrapping(boolean allowRawInjectionDespite * Ignore the given dependency type for autowiring: * for example, String. Default is none. */ - public void ignoreDependencyType(Class type) { + public void ignoreDependencyType(Class type) { this.ignoredDependencyTypes.add(type); } @@ -257,11 +258,10 @@ public void ignoreDependencyType(Class type) { * @see org.springframework.beans.factory.BeanFactoryAware * @see org.springframework.context.ApplicationContextAware */ - public void ignoreDependencyInterface(Class ifc) { + public void ignoreDependencyInterface(Class ifc) { this.ignoredDependencyInterfaces.add(ifc); } - @Override public void copyConfigurationFrom(ConfigurableBeanFactory otherFactory) { super.copyConfigurationFrom(otherFactory); @@ -285,7 +285,7 @@ public T createBean(Class beanClass) throws BeansException { // Use prototype bean definition, to avoid registering bean as dependent bean. RootBeanDefinition bd = new RootBeanDefinition(beanClass); bd.setScope(SCOPE_PROTOTYPE); - bd.allowCaching = false; + bd.allowCaching = ClassUtils.isCacheSafe(beanClass, getBeanClassLoader()); return (T) createBean(beanClass.getName(), bd, null); } @@ -293,7 +293,7 @@ public void autowireBean(Object existingBean) { // Use non-singleton bean definition, to avoid registering bean as dependent bean. RootBeanDefinition bd = new RootBeanDefinition(ClassUtils.getUserClass(existingBean)); bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); - bd.allowCaching = false; + bd.allowCaching = ClassUtils.isCacheSafe(bd.getBeanClass(), getBeanClassLoader()); BeanWrapper bw = new BeanWrapperImpl(existingBean); initBeanWrapper(bw); populateBean(bd.getBeanClass().getName(), bd, bw); @@ -305,14 +305,14 @@ public Object configureBean(Object existingBean, String beanName) throws BeansEx RootBeanDefinition bd = null; if (mbd instanceof RootBeanDefinition) { RootBeanDefinition rbd = (RootBeanDefinition) mbd; - if (rbd.isPrototype()) { - bd = rbd; - } + bd = (rbd.isPrototype() ? rbd : rbd.cloneBeanDefinition()); } - if (bd == null) { - bd = new RootBeanDefinition(mbd); + if (!mbd.isPrototype()) { + if (bd == null) { + bd = new RootBeanDefinition(mbd); + } bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); - bd.allowCaching = false; + bd.allowCaching = ClassUtils.isCacheSafe(ClassUtils.getUserClass(existingBean), getBeanClassLoader()); } BeanWrapper bw = new BeanWrapperImpl(existingBean); initBeanWrapper(bw); @@ -329,14 +329,14 @@ public Object resolveDependency(DependencyDescriptor descriptor, String beanName // Specialized methods for fine-grained control over the bean lifecycle //------------------------------------------------------------------------- - public Object createBean(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException { + public Object createBean(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException { // Use non-singleton bean definition, to avoid registering bean as dependent bean. RootBeanDefinition bd = new RootBeanDefinition(beanClass, autowireMode, dependencyCheck); bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); return createBean(beanClass.getName(), bd, null); } - public Object autowire(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException { + public Object autowire(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException { // Use non-singleton bean definition, to avoid registering bean as dependent bean. final RootBeanDefinition bd = new RootBeanDefinition(beanClass, autowireMode, dependencyCheck); bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); @@ -346,10 +346,8 @@ public Object autowire(Class beanClass, int autowireMode, boolean dependencyChec else { Object bean; final BeanFactory parent = this; - if (System.getSecurityManager() != null) { bean = AccessController.doPrivileged(new PrivilegedAction() { - public Object run() { return getInstantiationStrategy().instantiate(bd, null, parent); } @@ -358,7 +356,6 @@ public Object run() { else { bean = getInstantiationStrategy().instantiate(bd, null, parent); } - populateBean(beanClass.getName(), bd, new BeanWrapperImpl(bean)); return bean; } @@ -428,9 +425,7 @@ public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, St * @see #doCreateBean */ @Override - protected Object createBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) - throws BeanCreationException { - + protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException { if (logger.isDebugEnabled()) { logger.debug("Creating instance of bean '" + beanName + "'"); } @@ -490,7 +485,7 @@ protected Object doCreateBean(final String beanName, final RootBeanDefinition mb instanceWrapper = createBeanInstance(beanName, mbd, args); } final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null); - Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null); + Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null); // Allow post-processors to modify the merged bean definition. synchronized (mbd.postProcessingLock) { @@ -509,7 +504,7 @@ protected Object doCreateBean(final String beanName, final RootBeanDefinition mb logger.debug("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } - addSingletonFactory(beanName, new ObjectFactory() { + addSingletonFactory(beanName, new ObjectFactory() { public Object getObject() throws BeansException { return getEarlyBeanReference(beanName, mbd, bean); } @@ -572,7 +567,7 @@ else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { } @Override - protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Class... typesToMatch) { + protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Class... typesToMatch) { Class targetType = mbd.getTargetType(); if (targetType == null) { targetType = (mbd.getFactoryMethodName() != null ? getTypeForFactoryMethod(beanName, mbd, typesToMatch) : @@ -587,7 +582,7 @@ protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Clas for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; - Class predicted = ibp.predictBeanType(targetType, beanName); + Class predicted = ibp.predictBeanType(targetType, beanName); if (predicted != null && (typesToMatch.length != 1 || !FactoryBean.class.equals(typesToMatch[0]) || FactoryBean.class.isAssignableFrom(predicted))) { return predicted; @@ -599,7 +594,7 @@ protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Clas } /** - * Determine the bean type for the given bean definition which is based on + * Determine the target type for the given bean definition which is based on * a factory method. Only called if there is no singleton instance registered * for the target bean already. *

This implementation determines the type matching {@link #createBean}'s @@ -609,7 +604,7 @@ protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Clas * @param mbd the merged bean definition for the bean * @param typesToMatch the types to match in case of internal type matching purposes * (also signals that the returned {@code Class} will never be exposed to application code) - * @return the type for the bean if determinable, or {@code null} else + * @return the type for the bean if determinable, or {@code null} otherwise * @see #createBean */ protected Class getTypeForFactoryMethod(String beanName, RootBeanDefinition mbd, Class[] typesToMatch) { @@ -635,31 +630,61 @@ protected Class getTypeForFactoryMethod(String beanName, RootBeanDefinition m return null; } - List argumentValues = mbd.getConstructorArgumentValues().getGenericArgumentValues(); - Object[] args = new Object[argumentValues.size()]; - for (int i = 0; i < args.length; i++) { - args[i] = argumentValues.get(i).getValue(); - } - // 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; int minNrOfArgs = mbd.getConstructorArgumentValues().getArgumentCount(); Method[] candidates = ReflectionUtils.getUniqueDeclaredMethods(factoryClass); - Set> returnTypes = new HashSet>(1); for (Method factoryMethod : candidates) { if (Modifier.isStatic(factoryMethod.getModifiers()) == isStatic && factoryMethod.getName().equals(mbd.getFactoryMethodName()) && factoryMethod.getParameterTypes().length >= minNrOfArgs) { - Class returnType = GenericTypeResolver.resolveReturnTypeForGenericMethod(factoryMethod, args); - if (returnType != null) { - returnTypes.add(returnType); + // No declared type variables to inspect, so just process the standard return type. + if (factoryMethod.getTypeParameters().length > 0) { + try { + // Fully resolve parameter names and argument values. + Class[] paramTypes = factoryMethod.getParameterTypes(); + String[] paramNames = null; + ParameterNameDiscoverer pnd = getParameterNameDiscoverer(); + if (pnd != null) { + paramNames = pnd.getParameterNames(factoryMethod); + } + ConstructorArgumentValues cav = mbd.getConstructorArgumentValues(); + Set usedValueHolders = + new HashSet(paramTypes.length); + Object[] args = new Object[paramTypes.length]; + for (int i = 0; i < args.length; i++) { + ConstructorArgumentValues.ValueHolder valueHolder = cav.getArgumentValue( + i, paramTypes[i], (paramNames != null ? paramNames[i] : null), usedValueHolders); + if (valueHolder == null) { + valueHolder = cav.getGenericArgumentValue(null, null, usedValueHolders); + } + if (valueHolder != null) { + args[i] = valueHolder.getValue(); + usedValueHolders.add(valueHolder); + } + } + Class returnType = AutowireUtils.resolveReturnTypeForFactoryMethod( + factoryMethod, args, getBeanClassLoader()); + if (returnType != null) { + commonType = ClassUtils.determineCommonAncestor(returnType, commonType); + } + } + catch (Throwable ex) { + if (logger.isDebugEnabled()) { + logger.debug("Failed to resolve generic return type for factory method: " + ex); + } + } + } + else { + commonType = ClassUtils.determineCommonAncestor(factoryMethod.getReturnType(), commonType); } } } - if (returnTypes.size() == 1) { + if (commonType != null) { // Clear return type found: all factory methods return same type. - return returnTypes.iterator().next(); + return commonType; } else { // Ambiguous return types found: return null to indicate "not determinable". @@ -684,26 +709,24 @@ class Holder { Class value = null; } final Holder objectType = new Holder(); String factoryBeanName = mbd.getFactoryBeanName(); final String factoryMethodName = mbd.getFactoryMethodName(); + if (factoryBeanName != null && factoryMethodName != null) { // Try to obtain the FactoryBean's object type without instantiating it at all. BeanDefinition fbDef = getBeanDefinition(factoryBeanName); if (fbDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) fbDef).hasBeanClass()) { - Class fbClass = ((AbstractBeanDefinition) fbDef).getBeanClass(); - if (ClassUtils.isCglibProxyClass(fbClass)) { - // CGLIB subclass methods hide generic parameters. look at the superclass. - fbClass = fbClass.getSuperclass(); - } + // 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() { - 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); + new ReflectionUtils.MethodCallback() { + 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) { return objectType.value; } @@ -720,10 +743,14 @@ public void doWith(Method method) throws IllegalArgumentException, IllegalAccess if (objectType.value != null) { return objectType.value; } + 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); + return null; } /** @@ -742,7 +769,7 @@ protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); if (exposedObject == null) { - return exposedObject; + return null; } } } @@ -757,20 +784,20 @@ protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, /** * Obtain a "shortcut" singleton FactoryBean instance to use for a - * {@code getObjectType()} call, without full initialization - * of the FactoryBean. + * {@code getObjectType()} call, without full initialization of the FactoryBean. * @param beanName the name of the bean * @param mbd the bean definition for the bean * @return the FactoryBean instance, or {@code null} to indicate * that we couldn't obtain a shortcut FactoryBean instance */ - private FactoryBean getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) { + private FactoryBean getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) { synchronized (getSingletonMutex()) { BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName); if (bw != null) { - return (FactoryBean) bw.getWrappedInstance(); + return (FactoryBean) bw.getWrappedInstance(); } - if (isSingletonCurrentlyInCreation(beanName)) { + if (isSingletonCurrentlyInCreation(beanName) || + (mbd.getFactoryBeanName() != null && isSingletonCurrentlyInCreation(mbd.getFactoryBeanName()))) { return null; } Object instance = null; @@ -788,7 +815,7 @@ private FactoryBean getSingletonFactoryBeanForTypeCheck(String beanName, RootBea // Finished partial creation of this bean. afterSingletonCreation(beanName); } - FactoryBean fb = getFactoryBean(beanName, instance); + FactoryBean fb = getFactoryBean(beanName, instance); if (bw != null) { this.factoryBeanInstanceCache.put(beanName, bw); } @@ -798,14 +825,13 @@ private FactoryBean getSingletonFactoryBeanForTypeCheck(String beanName, RootBea /** * Obtain a "shortcut" non-singleton FactoryBean instance to use for a - * {@code getObjectType()} call, without full initialization - * of the FactoryBean. + * {@code getObjectType()} call, without full initialization of the FactoryBean. * @param beanName the name of the bean * @param mbd the bean definition for the bean * @return the FactoryBean instance, or {@code null} to indicate * that we couldn't obtain a shortcut FactoryBean instance */ - private FactoryBean getNonSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) { + private FactoryBean getNonSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) { if (isPrototypeCurrentlyInCreation(beanName)) { return null; } @@ -836,7 +862,7 @@ private FactoryBean getNonSingletonFactoryBeanForTypeCheck(String beanName, Root * @throws BeansException if any post-processing failed * @see MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition */ - protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class beanType, String beanName) + protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class beanType, String beanName) throws BeansException { try { @@ -887,7 +913,7 @@ protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition * @throws BeansException if any post-processing failed * @see InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation */ - protected Object applyBeanPostProcessorsBeforeInstantiation(Class beanClass, String beanName) + protected Object applyBeanPostProcessorsBeforeInstantiation(Class beanClass, String beanName) throws BeansException { for (BeanPostProcessor bp : getBeanPostProcessors()) { @@ -916,7 +942,7 @@ protected Object applyBeanPostProcessorsBeforeInstantiation(Class beanClass, Str */ protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) { // Make sure bean class is actually resolved at this point. - Class beanClass = resolveBeanClass(mbd, beanName); + Class beanClass = resolveBeanClass(mbd, beanName); if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, @@ -948,7 +974,7 @@ protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd } // Need to determine the constructor... - Constructor[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); + Constructor[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); if (ctors != null || mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { @@ -968,14 +994,14 @@ protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd * @throws org.springframework.beans.BeansException in case of errors * @see org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors */ - protected Constructor[] determineConstructorsFromBeanPostProcessors(Class beanClass, String beanName) + protected Constructor[] determineConstructorsFromBeanPostProcessors(Class beanClass, String beanName) throws BeansException { if (beanClass != null && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; - Constructor[] ctors = ibp.determineCandidateConstructors(beanClass, beanName); + Constructor[] ctors = ibp.determineCandidateConstructors(beanClass, beanName); if (ctors != null) { return ctors; } @@ -1046,7 +1072,7 @@ protected BeanWrapper instantiateUsingFactoryMethod( * @return BeanWrapper for the new instance */ protected BeanWrapper autowireConstructor( - String beanName, RootBeanDefinition mbd, Constructor[] ctors, Object[] explicitArgs) { + String beanName, RootBeanDefinition mbd, Constructor[] ctors, Object[] explicitArgs) { return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs); } @@ -1349,7 +1375,7 @@ protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrap MutablePropertyValues mpvs = null; List original; - if (System.getSecurityManager()!= null) { + if (System.getSecurityManager() != null) { if (bw instanceof BeanWrapperImpl) { ((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext()); } @@ -1642,7 +1668,7 @@ protected void removeSingleton(String beanName) { /** - * Special DependencyDescriptor variant for autowire="byType". + * Special DependencyDescriptor variant for Spring's good old autowire="byType" mode. * Always optional; never considering the parameter name for choosing a primary candidate. */ @SuppressWarnings("serial") 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 f93ea1b408..5094ad71dd 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,8 +38,8 @@ /** * Base class for concrete, full-fledged * {@link org.springframework.beans.factory.config.BeanDefinition} classes, - * factoring out common properties of {@link RootBeanDefinition} and - * {@link ChildBeanDefinition}. + * factoring out common properties of {@link GenericBeanDefinition}, + * {@link RootBeanDefinition} and {@link ChildBeanDefinition}. * *

The autowire constants match the ones defined in the * {@link org.springframework.beans.factory.config.AutowireCapableBeanFactory} @@ -123,11 +123,14 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess public static final int DEPENDENCY_CHECK_ALL = 3; /** - * Constant that indicates the container should attempt to infer the {@link - * #setDestroyMethodName destroy method name} for a bean as opposed to explicit - * specification of a method name. The value {@value} is specifically designed to - * include characters otherwise illegal in a method name, ensuring no possibility of - * collisions with legitimately named methods having the same name. + * Constant that indicates the container should attempt to infer the + * {@link #setDestroyMethodName destroy method name} for a bean as opposed to + * explicit specification of a method name. The value {@value} is specifically + * designed to include characters otherwise illegal in a method name, ensuring + * no possibility of collisions with legitimately named methods having the same + * name. + *

Currently, the method names detected during destroy method inference + * are "close" and "shutdown", if present on the specific bean class. */ public static final String INFER_METHOD = "(inferred)"; @@ -383,7 +386,7 @@ public Class getBeanClass() throws IllegalStateException { throw new IllegalStateException( "Bean class name [" + beanClassObject + "] has not been resolved into an actual Class"); } - return (Class) beanClassObject; + return (Class) beanClassObject; } public void setBeanClassName(String beanClassName) { @@ -393,7 +396,7 @@ public void setBeanClassName(String beanClassName) { public String getBeanClassName() { Object beanClassObject = this.beanClass; if (beanClassObject instanceof Class) { - return ((Class) beanClassObject).getName(); + return ((Class) beanClassObject).getName(); } else { return (String) beanClassObject; @@ -408,12 +411,12 @@ public String getBeanClassName() { * @return the resolved bean class * @throws ClassNotFoundException if the class name could be resolved */ - public Class resolveBeanClass(ClassLoader classLoader) throws ClassNotFoundException { + public Class resolveBeanClass(ClassLoader classLoader) throws ClassNotFoundException { String className = getBeanClassName(); if (className == null) { return null; } - Class resolvedClass = ClassUtils.forName(className, classLoader); + Class resolvedClass = ClassUtils.forName(className, classLoader); this.beanClass = resolvedClass; return resolvedClass; } @@ -551,8 +554,8 @@ public int getResolvedAutowireMode() { // Work out whether to apply setter autowiring or constructor autowiring. // If it has a no-arg constructor it's deemed to be setter autowiring, // otherwise we'll try constructor autowiring. - Constructor[] constructors = getBeanClass().getConstructors(); - for (Constructor constructor : constructors) { + Constructor[] constructors = getBeanClass().getConstructors(); + for (Constructor constructor : constructors) { if (constructor.getParameterTypes().length == 0) { return AUTOWIRE_BY_TYPE; } @@ -677,7 +680,8 @@ public void copyQualifiersFrom(AbstractBeanDefinition source) { /** * Specify whether to allow access to non-public constructors and methods, - * for the case of externalized metadata pointing to those. + * for the case of externalized metadata pointing to those. The default is + * {@code true}; switch this to {@code false} for public access only. *

This applies to constructor resolution, factory method resolution, * and also init/destroy methods. Bean property accessors have to be public * in any case and are not affected by this setting. @@ -699,7 +703,7 @@ public boolean isNonPublicAccessAllowed() { /** * Specify whether to resolve constructors in lenient mode ({@code true}, * which is the default) or to switch to strict resolution (throwing an exception - * in case of ambigious constructors that all match when converting the arguments, + * in case of ambiguous constructors that all match when converting the arguments, * whereas lenient mode would use the one with the 'closest' type matches). */ public void setLenientConstructorResolution(boolean lenientConstructorResolution) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java index b07e211d16..32db844c25 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,7 @@ public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable private ClassLoader beanClassLoader; - private Environment environment = new StandardEnvironment(); + private Environment environment; private BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator(); @@ -90,7 +90,7 @@ protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) { // Inherit Environment if possible if (this.registry instanceof EnvironmentCapable) { - this.environment = ((EnvironmentCapable)this.registry).getEnvironment(); + this.environment = ((EnvironmentCapable) this.registry).getEnvironment(); } else { this.environment = new StandardEnvironment(); 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 9e97ba2faf..779263092d 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-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -123,7 +123,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp /** Resolution strategy for expressions in bean definition values */ private BeanExpressionResolver beanExpressionResolver; - /** Spring 3.0 ConversionService to use instead of PropertyEditors */ + /** Spring ConversionService to use instead of PropertyEditors */ private ConversionService conversionService; /** Custom PropertyEditorRegistrars to apply to the beans of this factory */ @@ -159,10 +159,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp private final Map mergedBeanDefinitions = new ConcurrentHashMap(64); - /** - * Names of beans that have already been created at least once - * (using a ConcurrentHashMap as a Set) - */ + /** Names of beans that have already been created at least once */ private final Map alreadyCreated = new ConcurrentHashMap(64); /** Names of beans that are currently in creation */ @@ -275,77 +272,83 @@ protected T doGetBean( markBeanAsCreated(beanName); } - final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); - checkMergedBeanDefinition(mbd, beanName, args); - - // Guarantee initialization of beans that the current bean depends on. - String[] dependsOn = mbd.getDependsOn(); - if (dependsOn != null) { - for (String dependsOnBean : dependsOn) { - getBean(dependsOnBean); - registerDependentBean(dependsOnBean, beanName); - } - } - - // Create bean instance. - if (mbd.isSingleton()) { - sharedInstance = getSingleton(beanName, new ObjectFactory() { - public Object getObject() throws BeansException { - try { - return createBean(beanName, mbd, args); - } - catch (BeansException ex) { - // Explicitly remove instance from singleton cache: It might have been put there - // eagerly by the creation process, to allow for circular reference resolution. - // Also remove any beans that received a temporary reference to the bean. - destroySingleton(beanName); - throw ex; - } + try { + final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + checkMergedBeanDefinition(mbd, beanName, args); + + // Guarantee initialization of beans that the current bean depends on. + String[] dependsOn = mbd.getDependsOn(); + if (dependsOn != null) { + for (String dependsOnBean : dependsOn) { + getBean(dependsOnBean); + registerDependentBean(dependsOnBean, beanName); } - }); - bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); - } - - else if (mbd.isPrototype()) { - // It's a prototype -> create a new instance. - Object prototypeInstance = null; - try { - beforePrototypeCreation(beanName); - prototypeInstance = createBean(beanName, mbd, args); - } - finally { - afterPrototypeCreation(beanName); } - bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); - } - else { - String scopeName = mbd.getScope(); - final Scope scope = this.scopes.get(scopeName); - if (scope == null) { - throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'"); - } - try { - Object scopedInstance = scope.get(beanName, new ObjectFactory() { + // Create bean instance. + if (mbd.isSingleton()) { + sharedInstance = getSingleton(beanName, new ObjectFactory() { public Object getObject() throws BeansException { - beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } - finally { - afterPrototypeCreation(beanName); + catch (BeansException ex) { + // Explicitly remove instance from singleton cache: It might have been put there + // eagerly by the creation process, to allow for circular reference resolution. + // Also remove any beans that received a temporary reference to the bean. + destroySingleton(beanName); + throw ex; } } }); - bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); + bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); + } + + else if (mbd.isPrototype()) { + // It's a prototype -> create a new instance. + Object prototypeInstance = null; + try { + beforePrototypeCreation(beanName); + prototypeInstance = createBean(beanName, mbd, args); + } + finally { + afterPrototypeCreation(beanName); + } + bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } - catch (IllegalStateException ex) { - throw new BeanCreationException(beanName, - "Scope '" + scopeName + "' is not active for the current thread; " + - "consider defining a scoped proxy for this bean if you intend to refer to it from a singleton", - ex); + + else { + String scopeName = mbd.getScope(); + final Scope scope = this.scopes.get(scopeName); + if (scope == null) { + throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'"); + } + try { + Object scopedInstance = scope.get(beanName, new ObjectFactory() { + public Object getObject() throws BeansException { + beforePrototypeCreation(beanName); + try { + return createBean(beanName, mbd, args); + } + finally { + afterPrototypeCreation(beanName); + } + } + }); + bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); + } + catch (IllegalStateException ex) { + throw new BeanCreationException(beanName, + "Scope '" + scopeName + "' is not active for the current thread; " + + "consider defining a scoped proxy for this bean if you intend to refer to it from a singleton", + ex); + } } } + catch (BeansException ex) { + cleanupAfterBeanCreationFailure(beanName); + throw ex; + } } // Check if required type matches the type of the actual bean instance. @@ -497,8 +500,8 @@ else if (containsSingleton(beanName) && !containsBeanDefinition(beanName)) { // Retrieve corresponding bean definition. RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); - Class[] typesToMatch = (FactoryBean.class.equals(typeToMatch) ? - new Class[] {typeToMatch} : new Class[] {FactoryBean.class, typeToMatch}); + Class[] typesToMatch = (FactoryBean.class.equals(typeToMatch) ? + new Class[] {typeToMatch} : new Class[] {FactoryBean.class, typeToMatch}); // Check decorated bean definition, if any: We assume it'll be easier // to determine the decorated bean's type than the proxy's type. @@ -511,26 +514,32 @@ else if (containsSingleton(beanName) && !containsBeanDefinition(beanName)) { } } - Class beanClass = predictBeanType(beanName, mbd, typesToMatch); - if (beanClass == null) { + 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(beanClass)) { + 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. - Class type = getTypeForFactoryBean(beanName, mbd); - return (type != null && typeToMatch.isAssignableFrom(type)); - } - else { - return typeToMatch.isAssignableFrom(beanClass); + beanType = getTypeForFactoryBean(beanName, mbd); + if (beanType == null) { + return false; + } } } - else { - return !BeanFactoryUtils.isFactoryDereference(name) && - typeToMatch.isAssignableFrom(beanClass); + 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); } } @@ -1219,7 +1228,7 @@ protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName // Check validity of the usage of the args parameter. This can // only be used for prototypes constructed via a factory method. if (args != null && !mbd.isPrototype()) { - throw new BeanDefinitionStoreException( + throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName, "Can only specify arguments for the getBean method when referring to a prototype bean definition"); } } @@ -1335,8 +1344,8 @@ protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Clas * @param mbd the corresponding bean definition */ protected boolean isFactoryBean(String beanName, RootBeanDefinition mbd) { - Class beanClass = predictBeanType(beanName, mbd, FactoryBean.class); - return (beanClass != null && FactoryBean.class.isAssignableFrom(beanClass)); + Class beanType = predictBeanType(beanName, mbd, FactoryBean.class); + return (beanType != null && FactoryBean.class.isAssignableFrom(beanType)); } /** @@ -1379,7 +1388,17 @@ protected Class getTypeForFactoryBean(String beanName, RootBeanDefinition mbd * @param beanName the name of the bean */ protected void markBeanAsCreated(String beanName) { - this.alreadyCreated.put(beanName, Boolean.TRUE); + if (!this.alreadyCreated.containsKey(beanName)) { + this.alreadyCreated.put(beanName, Boolean.TRUE); + } + } + + /** + * Perform appropriate cleanup of cached metadata after bean creation failed. + * @param beanName the name of the bean + */ + protected void cleanupAfterBeanCreationFailure(String beanName) { + this.alreadyCreated.remove(beanName); } /** 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 f8ffd5798b..1ef3ceafe6 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-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,20 +23,27 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Proxy; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.util.Arrays; import java.util.Comparator; import java.util.Set; +import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.TypedStringValue; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** - * Utility class that contains various methods useful for - * the implementation of autowire-capable bean factories. + * Utility class that contains various methods useful for the implementation of + * autowire-capable bean factories. * * @author Juergen Hoeller * @author Mark Fisher + * @author Sam Brannen * @since 1.1.2 * @see AbstractAutowireCapableBeanFactory */ @@ -49,9 +56,9 @@ abstract class AutowireUtils { * decreasing number of arguments. * @param constructors the constructor array to sort */ - public static void sortConstructors(Constructor[] constructors) { - Arrays.sort(constructors, new Comparator() { - public int compare(Constructor c1, Constructor c2) { + public static void sortConstructors(Constructor[] constructors) { + Arrays.sort(constructors, new Comparator>() { + public int compare(Constructor c1, Constructor c2) { boolean p1 = Modifier.isPublic(c1.getModifiers()); boolean p2 = Modifier.isPublic(c2.getModifiers()); if (p1 != p2) { @@ -103,7 +110,7 @@ public static boolean isExcludedFromDependencyCheck(PropertyDescriptor pd) { } // It was declared by CGLIB, but we might still want to autowire it // if it was actually declared by the superclass. - Class superclass = wm.getDeclaringClass().getSuperclass(); + Class superclass = wm.getDeclaringClass().getSuperclass(); return !ClassUtils.hasMethod(superclass, wm.getName(), wm.getParameterTypes()); } @@ -114,11 +121,11 @@ public static boolean isExcludedFromDependencyCheck(PropertyDescriptor pd) { * @param interfaces the Set of interfaces (Class objects) * @return whether the setter method is defined by an interface */ - public static boolean isSetterDefinedInInterface(PropertyDescriptor pd, Set interfaces) { + public static boolean isSetterDefinedInInterface(PropertyDescriptor pd, Set> interfaces) { Method setter = pd.getWriteMethod(); if (setter != null) { - Class targetClass = setter.getDeclaringClass(); - for (Class ifc : interfaces) { + Class targetClass = setter.getDeclaringClass(); + for (Class ifc : interfaces) { if (ifc.isAssignableFrom(targetClass) && ClassUtils.hasMethod(ifc, setter.getName(), setter.getParameterTypes())) { return true; @@ -135,12 +142,12 @@ public static boolean isSetterDefinedInInterface(PropertyDescriptor pd, Set requiredType) { if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) { - ObjectFactory factory = (ObjectFactory) autowiringValue; + ObjectFactory factory = (ObjectFactory) autowiringValue; if (autowiringValue instanceof Serializable && requiredType.isInterface()) { autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(), - new Class[] {requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory)); + new Class[] {requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory)); } else { return factory.getObject(); @@ -149,6 +156,124 @@ public static Object resolveAutowiringValue(Object autowiringValue, Class requir return autowiringValue; } + /** + * 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 + * 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}. + *

{@code public static  T createProxy(Class clazz)}
+ *

Possible Return Values

+ *
    + *
  • the target return type, if it can be inferred
  • + *
  • the {@linkplain Method#getReturnType() standard return type}, if + * the given {@code method} does not declare any {@linkplain + * Method#getTypeParameters() formal type variables}
  • + *
  • the {@linkplain Method#getReturnType() standard return type}, if the + * target return type cannot be inferred (e.g., due to type erasure)
  • + *
  • {@code null}, if the length of the given arguments array is shorter + * than the length of the {@linkplain + * Method#getGenericParameterTypes() formal argument list} for the given + * method
  • + *
+ * @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} + * @since 3.2.5 + */ + public static Class resolveReturnTypeForFactoryMethod(Method method, Object[] args, ClassLoader classLoader) { + Assert.notNull(method, "Method must not be null"); + Assert.notNull(args, "Argument array must not be null"); + Assert.notNull(classLoader, "ClassLoader must not be null"); + + TypeVariable[] declaredTypeVariables = method.getTypeParameters(); + Type genericReturnType = method.getGenericReturnType(); + Type[] methodParameterTypes = method.getGenericParameterTypes(); + Assert.isTrue(args.length == methodParameterTypes.length, "Argument array does not match parameter count"); + + // Ensure that the type variable (e.g., T) is declared directly on the method + // itself (e.g., via ), not on the enclosing class or interface. + boolean locallyDeclaredTypeVariableMatchesReturnType = false; + for (TypeVariable currentTypeVariable : declaredTypeVariables) { + if (currentTypeVariable.equals(genericReturnType)) { + locallyDeclaredTypeVariableMatchesReturnType = true; + break; + } + } + + if (locallyDeclaredTypeVariableMatchesReturnType) { + for (int i = 0; i < methodParameterTypes.length; i++) { + Type methodParameterType = methodParameterTypes[i]; + Object arg = args[i]; + if (methodParameterType.equals(genericReturnType)) { + if (arg instanceof TypedStringValue) { + TypedStringValue typedValue = ((TypedStringValue) arg); + if (typedValue.hasTargetType()) { + return typedValue.getTargetType(); + } + try { + return typedValue.resolveTargetType(classLoader); + } + catch (ClassNotFoundException ex) { + throw new IllegalStateException("Failed to resolve value type [" + + typedValue.getTargetTypeName() + "] for factory method argument", ex); + } + } + // Only consider argument type if it is a simple value... + if (arg != null && !(arg instanceof BeanMetadataElement)) { + return arg.getClass(); + } + return method.getReturnType(); + } + else if (methodParameterType instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) methodParameterType; + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + for (Type typeArg : actualTypeArguments) { + if (typeArg.equals(genericReturnType)) { + if (arg instanceof Class) { + return (Class) arg; + } + else { + String className = null; + if (arg instanceof String) { + className = (String) arg; + } + else if (arg instanceof TypedStringValue) { + TypedStringValue typedValue = ((TypedStringValue) arg); + String targetTypeName = typedValue.getTargetTypeName(); + if (targetTypeName == null || Class.class.getName().equals(targetTypeName)) { + className = typedValue.getValue(); + } + } + if (className != null) { + try { + return ClassUtils.forName(className, classLoader); + } + catch (ClassNotFoundException ex) { + throw new IllegalStateException("Could not resolve class name [" + arg + + "] for factory method argument", ex); + } + } + // Consider adding logic to determine the class of the typeArg, if possible. + // For now, just fall back... + return method.getReturnType(); + } + } + } + } + } + } + + // Fall back... + return method.getReturnType(); + } + /** * Reflective InvocationHandler for lazy access to the current target object. @@ -156,9 +281,9 @@ public static Object resolveAutowiringValue(Object autowiringValue, Class requir @SuppressWarnings("serial") private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable { - private final ObjectFactory objectFactory; + private final ObjectFactory objectFactory; - public ObjectFactoryDelegatingInvocationHandler(ObjectFactory objectFactory) { + public ObjectFactoryDelegatingInvocationHandler(ObjectFactory objectFactory) { this.objectFactory = objectFactory; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReader.java index f5d1e77781..bd8b8f51b5 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ /** * Simple interface for bean definition readers. - * Specifies load methods with Resource parameters. + * Specifies load methods with Resource and String location parameters. * *

Concrete bean definition readers can of course add additional * load and register methods for bean definitions, specific to @@ -45,23 +45,23 @@ public interface BeanDefinitionReader { */ BeanDefinitionRegistry getRegistry(); - /** - * Return the resource loader to use for resource locations. - * Can be checked for the ResourcePatternResolver interface and cast - * accordingly, for loading multiple resources for a given resource pattern. - *

Null suggests that absolute resource loading is not available - * for this bean definition reader. - *

This is mainly meant to be used for importing further resources - * from within a bean definition resource, for example via the "import" - * tag in XML bean definitions. It is recommended, however, to apply - * such imports relative to the defining resource; only explicit full - * resource locations will trigger absolute resource loading. - *

There is also a {@code loadBeanDefinitions(String)} method available, - * for loading bean definitions from a resource location (or location pattern). - * This is a convenience to avoid explicit ResourceLoader handling. - * @see #loadBeanDefinitions(String) - * @see org.springframework.core.io.support.ResourcePatternResolver - */ + /** + * Return the resource loader to use for resource locations. + * Can be checked for the ResourcePatternResolver interface and cast + * accordingly, for loading multiple resources for a given resource pattern. + *

Null suggests that absolute resource loading is not available + * for this bean definition reader. + *

This is mainly meant to be used for importing further resources + * from within a bean definition resource, for example via the "import" + * tag in XML bean definitions. It is recommended, however, to apply + * such imports relative to the defining resource; only explicit full + * resource locations will trigger absolute resource loading. + *

There is also a {@code loadBeanDefinitions(String)} method available, + * for loading bean definitions from a resource location (or location pattern). + * This is a convenience to avoid explicit ResourceLoader handling. + * @see #loadBeanDefinitions(String) + * @see org.springframework.core.io.support.ResourcePatternResolver + */ ResourceLoader getResourceLoader(); /** diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java index 4ee6d218fa..18aaa23d03 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,6 +81,7 @@ public BeanDefinitionValueResolver( this.typeConverter = typeConverter; } + /** * Given a PropertyValue, return a value, resolving any references to other * beans in the factory if necessary. The value could be: @@ -123,12 +124,14 @@ else if (value instanceof BeanDefinitionHolder) { else if (value instanceof BeanDefinition) { // Resolve plain BeanDefinition, without contained name: use dummy name. BeanDefinition bd = (BeanDefinition) value; - return resolveInnerBean(argName, "(inner bean)", bd); + String innerBeanName = "(inner bean)" + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR + + ObjectUtils.getIdentityHexString(bd); + return resolveInnerBean(argName, innerBeanName, bd); } else if (value instanceof ManagedArray) { // May need to resolve contained runtime references. ManagedArray array = (ManagedArray) value; - Class elementType = array.resolvedElementType; + Class elementType = array.resolvedElementType; if (elementType == null) { String elementTypeName = array.getElementTypeName(); if (StringUtils.hasText(elementTypeName)) { @@ -164,7 +167,7 @@ else if (value instanceof ManagedMap) { else if (value instanceof ManagedProperties) { Properties original = (Properties) value; Properties copy = new Properties(); - for (Map.Entry propEntry : original.entrySet()) { + for (Map.Entry propEntry : original.entrySet()) { Object propKey = propEntry.getKey(); Object propValue = propEntry.getValue(); if (propKey instanceof TypedStringValue) { @@ -260,19 +263,21 @@ private Object resolveInnerBean(Object argName, String innerBeanName, BeanDefini if (mbd.isSingleton()) { actualInnerBeanName = adaptInnerBeanName(innerBeanName); } + this.beanFactory.registerContainedBean(actualInnerBeanName, this.beanName); // Guarantee initialization of beans that the inner bean depends on. String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for (String dependsOnBean : dependsOn) { - this.beanFactory.getBean(dependsOnBean); this.beanFactory.registerDependentBean(dependsOnBean, actualInnerBeanName); + this.beanFactory.getBean(dependsOnBean); } } + // Actually create the inner bean instance now... Object innerBean = this.beanFactory.createBean(actualInnerBeanName, mbd, null); - this.beanFactory.registerContainedBean(actualInnerBeanName, this.beanName); if (innerBean instanceof FactoryBean) { - boolean synthetic = (mbd != null && mbd.isSynthetic()); - return this.beanFactory.getObjectFromFactoryBean((FactoryBean) innerBean, actualInnerBeanName, !synthetic); + boolean synthetic = mbd.isSynthetic(); + return this.beanFactory.getObjectFromFactoryBean( + (FactoryBean) innerBean, actualInnerBeanName, !synthetic); } else { return innerBean; @@ -335,7 +340,7 @@ private Object resolveReference(Object argName, RuntimeBeanReference ref) { /** * For each element in the managed array, resolve reference if necessary. */ - private Object resolveManagedArray(Object argName, List ml, Class elementType) { + private Object resolveManagedArray(Object argName, List ml, Class elementType) { Object resolved = Array.newInstance(elementType, ml.size()); for (int i = 0; i < ml.size(); i++) { Array.set(resolved, i, @@ -347,7 +352,7 @@ private Object resolveManagedArray(Object argName, List ml, Class elementType /** * For each element in the managed list, resolve reference if necessary. */ - private List resolveManagedList(Object argName, List ml) { + private List resolveManagedList(Object argName, List ml) { List resolved = new ArrayList(ml.size()); for (int i = 0; i < ml.size(); i++) { resolved.add( @@ -359,7 +364,7 @@ private List resolveManagedList(Object argName, List ml) { /** * For each element in the managed set, resolve reference if necessary. */ - private Set resolveManagedSet(Object argName, Set ms) { + private Set resolveManagedSet(Object argName, Set ms) { Set resolved = new LinkedHashSet(ms.size()); int i = 0; for (Object m : ms) { @@ -372,9 +377,9 @@ private Set resolveManagedSet(Object argName, Set ms) { /** * For each element in the managed map, resolve reference if necessary. */ - private Map resolveManagedMap(Object argName, Map mm) { + private Map resolveManagedMap(Object argName, Map mm) { Map resolved = new LinkedHashMap(mm.size()); - for (Map.Entry entry : mm.entrySet()) { + for (Map.Entry entry : mm.entrySet()) { Object resolvedKey = resolveValueIfNecessary(argName, entry.getKey()); Object resolvedValue = resolveValueIfNecessary( new KeyedArgName(argName, entry.getKey()), entry.getValue()); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java index dc601c3bd4..076e209838 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,7 +23,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; - +import org.springframework.cglib.core.SpringNamingPolicy; import org.springframework.cglib.proxy.Callback; import org.springframework.cglib.proxy.CallbackFilter; import org.springframework.cglib.proxy.Enhancer; @@ -33,8 +33,9 @@ /** * Default object instantiation strategy for use in BeanFactories. - * Uses CGLIB to generate subclasses dynamically if methods need to be - * overridden by the container, to implement Method Injection. + * + *

Uses CGLIB to generate subclasses dynamically if methods need to be + * overridden by the container to implement Method Injection. * * @author Rod Johnson * @author Juergen Hoeller @@ -50,13 +51,13 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt /** * Index in the CGLIB callback array for a method that should - * be overridden to provide method lookup. + * be overridden to provide method lookup. */ private static final int LOOKUP_OVERRIDE = 1; /** * Index in the CGLIB callback array for a method that should - * be overridden using generic Methodreplacer functionality. + * be overridden using generic method replacer functionality. */ private static final int METHOD_REPLACER = 2; @@ -72,7 +73,7 @@ protected Object instantiateWithMethodInjection( @Override protected Object instantiateWithMethodInjection( RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, - Constructor ctor, Object[] args) { + Constructor ctor, Object[] args) { return new CglibSubclassCreator(beanDefinition, owner).instantiate(ctor, args); } @@ -96,17 +97,18 @@ public CglibSubclassCreator(RootBeanDefinition beanDefinition, BeanFactory owner } /** - * Create a new instance of a dynamically generated subclasses implementing the + * Create a new instance of a dynamically generated subclass implementing the * required lookups. * @param ctor constructor to use. If this is {@code null}, use the * no-arg constructor (no parameterization, or Setter Injection) * @param args arguments to use for the constructor. - * Ignored if the ctor parameter is {@code null}. - * @return new instance of the dynamically generated class + * Ignored if the {@code ctor} parameter is {@code null}. + * @return new instance of the dynamically generated subclass */ - public Object instantiate(Constructor ctor, Object[] args) { + public Object instantiate(Constructor ctor, Object[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.beanDefinition.getBeanClass()); + enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); enhancer.setCallbackFilter(new CallbackFilterImpl()); enhancer.setCallbacks(new Callback[] { NoOp.INSTANCE, @@ -114,9 +116,7 @@ public Object instantiate(Constructor ctor, Object[] args) { new ReplaceOverrideMethodInterceptor() }); - return (ctor == null) ? - enhancer.create() : - enhancer.create(ctor.getParameterTypes(), args); + return (ctor != null ? enhancer.create(ctor.getParameterTypes(), args) : enhancer.create()); } 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 b03ca67dcd..72ab68bdf5 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-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -98,7 +98,7 @@ public ChildBeanDefinition( * @param pvs the property values to apply */ public ChildBeanDefinition( - String parentName, Class beanClass, ConstructorArgumentValues cargs, MutablePropertyValues pvs) { + String parentName, Class beanClass, ConstructorArgumentValues cargs, MutablePropertyValues pvs) { super(cargs, pvs); this.parentName = parentName; 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 384866ffdd..e97652c565 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-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. @@ -54,12 +54,9 @@ import org.springframework.util.StringUtils; /** - * Helper class for resolving constructors and factory methods. + * Delegate for resolving constructors and factory methods. * Performs constructor resolution through argument matching. * - *

Operates on an {@link AbstractBeanFactory} and an {@link InstantiationStrategy}. - * Used by {@link AbstractAutowireCapableBeanFactory}. - * * @author Juergen Hoeller * @author Rob Harrop * @author Mark Fisher @@ -103,12 +100,12 @@ public ConstructorResolver(AbstractAutowireCapableBeanFactory beanFactory) { * @return a BeanWrapper for the new instance */ public BeanWrapper autowireConstructor( - final String beanName, final RootBeanDefinition mbd, Constructor[] chosenCtors, final Object[] explicitArgs) { + final String beanName, final RootBeanDefinition mbd, Constructor[] chosenCtors, final Object[] explicitArgs) { BeanWrapperImpl bw = new BeanWrapperImpl(); this.beanFactory.initBeanWrapper(bw); - Constructor constructorToUse = null; + Constructor constructorToUse = null; ArgumentsHolder argsHolderToUse = null; Object[] argsToUse = null; @@ -118,7 +115,7 @@ public BeanWrapper autowireConstructor( else { Object[] argsToResolve = null; synchronized (mbd.constructorArgumentLock) { - constructorToUse = (Constructor) mbd.resolvedConstructorOrFactoryMethod; + constructorToUse = (Constructor) mbd.resolvedConstructorOrFactoryMethod; if (constructorToUse != null && mbd.constructorArgumentsResolved) { // Found a cached constructor... argsToUse = mbd.resolvedConstructorArguments; @@ -149,9 +146,9 @@ public BeanWrapper autowireConstructor( } // Take specified constructors, if any. - Constructor[] candidates = chosenCtors; + Constructor[] candidates = chosenCtors; if (candidates == null) { - Class beanClass = mbd.getBeanClass(); + Class beanClass = mbd.getBeanClass(); try { candidates = (mbd.isNonPublicAccessAllowed() ? beanClass.getDeclaredConstructors() : beanClass.getConstructors()); @@ -159,17 +156,17 @@ public BeanWrapper autowireConstructor( catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Resolution of declared constructors on bean Class [" + beanClass.getName() + - "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex); + "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex); } } AutowireUtils.sortConstructors(candidates); int minTypeDiffWeight = Integer.MAX_VALUE; - Set ambiguousConstructors = null; + Set> ambiguousConstructors = null; List causes = null; for (int i = 0; i < candidates.length; i++) { Constructor candidate = candidates[i]; - Class[] paramTypes = candidate.getParameterTypes(); + Class[] paramTypes = candidate.getParameterTypes(); if (constructorToUse != null && argsToUse.length > paramTypes.length) { // Already found greedy constructor that can be satisfied -> @@ -185,7 +182,7 @@ public BeanWrapper autowireConstructor( try { String[] paramNames = null; if (constructorPropertiesAnnotationAvailable) { - paramNames = ConstructorPropertiesChecker.evaluateAnnotation(candidate, paramTypes.length); + paramNames = ConstructorPropertiesChecker.evaluate(candidate, paramTypes.length); } if (paramNames == null) { ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer(); @@ -239,7 +236,7 @@ public BeanWrapper autowireConstructor( } else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) { if (ambiguousConstructors == null) { - ambiguousConstructors = new LinkedHashSet(); + ambiguousConstructors = new LinkedHashSet>(); ambiguousConstructors.add(constructorToUse); } ambiguousConstructors.add(candidate); @@ -267,7 +264,7 @@ else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) Object beanInstance; if (System.getSecurityManager() != null) { - final Constructor ctorToUse = constructorToUse; + final Constructor ctorToUse = constructorToUse; final Object[] argumentsToUse = argsToUse; beanInstance = AccessController.doPrivileged(new PrivilegedAction() { public Object run() { @@ -295,18 +292,22 @@ public Object run() { * @param mbd the bean definition to check */ public void resolveFactoryMethodIfPossible(RootBeanDefinition mbd) { - Class factoryClass; + Class factoryClass; + boolean isStatic; if (mbd.getFactoryBeanName() != null) { factoryClass = this.beanFactory.getType(mbd.getFactoryBeanName()); + isStatic = false; } else { factoryClass = mbd.getBeanClass(); + isStatic = true; } factoryClass = ClassUtils.getUserClass(factoryClass); - Method[] candidates = ReflectionUtils.getAllDeclaredMethods(factoryClass); + + Method[] candidates = getCandidateMethods(factoryClass, mbd); Method uniqueCandidate = null; for (Method candidate : candidates) { - if (mbd.isFactoryMethod(candidate)) { + if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate)) { if (uniqueCandidate == null) { uniqueCandidate = candidate; } @@ -321,6 +322,27 @@ else if (!Arrays.equals(uniqueCandidate.getParameterTypes(), candidate.getParame } } + /** + * Retrieve all candidate methods for the given class, considering + * the {@link RootBeanDefinition#isNonPublicAccessAllowed()} flag. + * Called as the starting point for factory method determination. + */ + private Method[] getCandidateMethods(final Class factoryClass, final RootBeanDefinition mbd) { + if (System.getSecurityManager() != null) { + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Method[] run() { + return (mbd.isNonPublicAccessAllowed() ? + ReflectionUtils.getAllDeclaredMethods(factoryClass) : factoryClass.getMethods()); + } + }); + } + else { + return (mbd.isNonPublicAccessAllowed() ? + ReflectionUtils.getAllDeclaredMethods(factoryClass) : factoryClass.getMethods()); + } + } + /** * Instantiate the bean using a named factory method. The method may be static, if the * bean definition parameter specifies a class, rather than a "factory-bean", or @@ -336,12 +358,14 @@ else if (!Arrays.equals(uniqueCandidate.getParameterTypes(), candidate.getParame * method, or {@code null} if none (-> use constructor argument values from bean definition) * @return a BeanWrapper for the new instance */ - public BeanWrapper instantiateUsingFactoryMethod(final String beanName, final RootBeanDefinition mbd, final Object[] explicitArgs) { + public BeanWrapper instantiateUsingFactoryMethod( + final String beanName, final RootBeanDefinition mbd, final Object[] explicitArgs) { + BeanWrapperImpl bw = new BeanWrapperImpl(); this.beanFactory.initBeanWrapper(bw); Object factoryBean; - Class factoryClass; + Class factoryClass; boolean isStatic; String factoryBeanName = mbd.getFactoryBeanName(); @@ -353,7 +377,7 @@ public BeanWrapper instantiateUsingFactoryMethod(final String beanName, final Ro factoryBean = this.beanFactory.getBean(factoryBeanName); if (factoryBean == null) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, - "factory-bean '" + factoryBeanName + "' returned null"); + "factory-bean '" + factoryBeanName + "' (or a BeanPostProcessor involved) returned null"); } factoryClass = factoryBean.getClass(); isStatic = false; @@ -397,27 +421,11 @@ public BeanWrapper instantiateUsingFactoryMethod(final String beanName, final Ro // Need to determine the factory method... // Try all methods with this name to see if they match the given arguments. factoryClass = ClassUtils.getUserClass(factoryClass); - Method[] rawCandidates; - - final Class factoryClazz = factoryClass; - if (System.getSecurityManager() != null) { - rawCandidates = AccessController.doPrivileged(new PrivilegedAction() { - public Method[] run() { - return (mbd.isNonPublicAccessAllowed() ? - ReflectionUtils.getAllDeclaredMethods(factoryClazz) : factoryClazz.getMethods()); - } - }); - } - else { - rawCandidates = (mbd.isNonPublicAccessAllowed() ? - ReflectionUtils.getAllDeclaredMethods(factoryClazz) : factoryClazz.getMethods()); - } + Method[] rawCandidates = getCandidateMethods(factoryClass, mbd); List candidateSet = new ArrayList(); for (Method candidate : rawCandidates) { - if (Modifier.isStatic(candidate.getModifiers()) == isStatic && - candidate.getName().equals(mbd.getFactoryMethodName()) && - mbd.isFactoryMethod(candidate)) { + if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate)) { candidateSet.add(candidate); } } @@ -445,7 +453,7 @@ public Method[] run() { for (int i = 0; i < candidates.length; i++) { Method candidate = candidates[i]; - Class[] paramTypes = candidate.getParameterTypes(); + Class[] paramTypes = candidate.getParameterTypes(); if (paramTypes.length >= minNrOfArgs) { ArgumentsHolder argsHolder; @@ -503,7 +511,15 @@ public Method[] run() { minTypeDiffWeight = typeDiffWeight; ambiguousFactoryMethods = null; } - else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight) { + // Find out about ambiguity: In case of the same type difference weight + // for methods with the same number of parameters, collect such candidates + // and eventually raise an ambiguity exception. + // However, only perform that check in non-lenient constructor resolution mode, + // and explicitly ignore overridden methods (with the same parameter signature). + else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight && + !mbd.isLenientConstructorResolution() && + paramTypes.length == factoryMethodToUse.getParameterTypes().length && + !Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes())) { if (ambiguousFactoryMethods == null) { ambiguousFactoryMethods = new LinkedHashSet(); ambiguousFactoryMethods.add(factoryMethodToUse); @@ -514,24 +530,30 @@ else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight) { } if (factoryMethodToUse == null) { - boolean hasArgs = (resolvedValues.getArgumentCount() > 0); - String argDesc = ""; - if (hasArgs) { - List argTypes = new ArrayList(); - for (ValueHolder value : resolvedValues.getIndexedArgumentValues().values()) { - String argType = (value.getType() != null ? - ClassUtils.getShortName(value.getType()) : value.getValue().getClass().getSimpleName()); + List argTypes = new ArrayList(minNrOfArgs); + if (explicitArgs != null) { + for (Object arg : explicitArgs) { + argTypes.add(arg != null ? arg.getClass().getSimpleName() : "null"); + } + } + else { + Set valueHolders = new LinkedHashSet(resolvedValues.getArgumentCount()); + valueHolders.addAll(resolvedValues.getIndexedArgumentValues().values()); + valueHolders.addAll(resolvedValues.getGenericArgumentValues()); + for (ValueHolder value : valueHolders) { + String argType = (value.getType() != null ? ClassUtils.getShortName(value.getType()) : + (value.getValue() != null ? value.getValue().getClass().getSimpleName() : "null")); argTypes.add(argType); } - argDesc = StringUtils.collectionToCommaDelimitedString(argTypes); } + String argDesc = StringUtils.collectionToCommaDelimitedString(argTypes); throw new BeanCreationException(mbd.getResourceDescription(), beanName, "No matching factory method found: " + (mbd.getFactoryBeanName() != null ? "factory bean '" + mbd.getFactoryBeanName() + "'; " : "") + "factory method '" + mbd.getFactoryMethodName() + "(" + argDesc + ")'. " + "Check that a method with the specified name " + - (hasArgs ? "and arguments " : "") + + (minNrOfArgs > 0 ? "and arguments " : "") + "exists and that it is " + (isStatic ? "static" : "non-static") + "."); } @@ -540,7 +562,7 @@ else if (void.class.equals(factoryMethodToUse.getReturnType())) { "Invalid factory method '" + mbd.getFactoryMethodName() + "': needs to have a non-void return type!"); } - else if (ambiguousFactoryMethods != null && !mbd.isLenientConstructorResolution()) { + else if (ambiguousFactoryMethods != null) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Ambiguous factory method matches found in bean '" + beanName + "' " + "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " + @@ -591,8 +613,8 @@ private int resolveConstructorArguments( String beanName, RootBeanDefinition mbd, BeanWrapper bw, ConstructorArgumentValues cargs, ConstructorArgumentValues resolvedValues) { - TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ? - this.beanFactory.getCustomTypeConverter() : bw); + TypeConverter customConverter = this.beanFactory.getCustomTypeConverter(); + TypeConverter converter = (customConverter != null ? customConverter : bw); BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter); @@ -644,12 +666,12 @@ private int resolveConstructorArguments( */ private ArgumentsHolder createArgumentArray( String beanName, RootBeanDefinition mbd, ConstructorArgumentValues resolvedValues, - BeanWrapper bw, Class[] paramTypes, String[] paramNames, Object methodOrCtor, + BeanWrapper bw, Class[] paramTypes, String[] paramNames, Object methodOrCtor, boolean autowiring) throws UnsatisfiedDependencyException { String methodType = (methodOrCtor instanceof Constructor ? "constructor" : "factory method"); - TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ? - this.beanFactory.getCustomTypeConverter() : bw); + TypeConverter customConverter = this.beanFactory.getCustomTypeConverter(); + TypeConverter converter = (customConverter != null ? customConverter : bw); ArgumentsHolder args = new ArgumentsHolder(paramTypes.length); Set usedValueHolders = @@ -750,12 +772,13 @@ private ArgumentsHolder createArgumentArray( private Object[] resolvePreparedArguments( String beanName, RootBeanDefinition mbd, BeanWrapper bw, Member methodOrCtor, Object[] argsToResolve) { - Class[] paramTypes = (methodOrCtor instanceof Method ? - ((Method) methodOrCtor).getParameterTypes() : ((Constructor) methodOrCtor).getParameterTypes()); - TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ? - this.beanFactory.getCustomTypeConverter() : bw); + TypeConverter customConverter = this.beanFactory.getCustomTypeConverter(); + TypeConverter converter = (customConverter != null ? customConverter : bw); BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter); + Class[] paramTypes = (methodOrCtor instanceof Method ? + ((Method) methodOrCtor).getParameterTypes() : ((Constructor) methodOrCtor).getParameterTypes()); + Object[] resolvedArgs = new Object[argsToResolve.length]; for (int argIndex = 0; argIndex < argsToResolve.length; argIndex++) { Object argValue = argsToResolve[argIndex]; @@ -822,7 +845,7 @@ public ArgumentsHolder(Object[] args) { this.preparedArguments = args; } - public int getTypeDifferenceWeight(Class[] paramTypes) { + public int getTypeDifferenceWeight(Class[] paramTypes) { // If valid arguments found, determine type difference weight. // Try type difference weight on both the converted arguments and // the raw arguments. If the raw weight is better, use it. @@ -832,7 +855,7 @@ public int getTypeDifferenceWeight(Class[] paramTypes) { return (rawTypeDiffWeight < typeDiffWeight ? rawTypeDiffWeight : typeDiffWeight); } - public int getAssignabilityWeight(Class[] paramTypes) { + public int getAssignabilityWeight(Class[] paramTypes) { for (int i = 0; i < paramTypes.length; i++) { if (!ClassUtils.isAssignableValue(paramTypes[i], this.arguments[i])) { return Integer.MAX_VALUE; @@ -873,7 +896,7 @@ private static class AutowiredArgumentMarker { */ private static class ConstructorPropertiesChecker { - public static String[] evaluateAnnotation(Constructor candidate, int paramCount) { + public static String[] evaluate(Constructor candidate, int paramCount) { ConstructorProperties cp = candidate.getAnnotation(ConstructorProperties.class); if (cp != null) { String[] names = cp.value(); 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 e88e04f469..07c1620bfb 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-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,6 @@ import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -60,6 +59,7 @@ import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -103,9 +103,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto private static Class javaxInjectProviderClass = null; static { - ClassLoader cl = DefaultListableBeanFactory.class.getClassLoader(); try { - javaxInjectProviderClass = cl.loadClass("javax.inject.Provider"); + javaxInjectProviderClass = + ClassUtils.forName("javax.inject.Provider", DefaultListableBeanFactory.class.getClassLoader()); } catch (ClassNotFoundException ex) { // JSR-330 API not available - Provider interface simply not supported then. @@ -135,14 +135,14 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto /** Map of bean definition objects, keyed by bean name */ private final Map beanDefinitionMap = new ConcurrentHashMap(64); - /** Map of singleton and non-singleton bean names keyed by dependency type */ + /** Map of singleton and non-singleton bean names, keyed by dependency type */ private final Map, String[]> allBeanNamesByType = new ConcurrentHashMap, String[]>(64); - /** Map of singleton-only bean names keyed by dependency type */ + /** Map of singleton-only bean names, keyed by dependency type */ private final Map, String[]> singletonBeanNamesByType = new ConcurrentHashMap, String[]>(64); /** List of bean definition names, in registration order */ - private final List beanDefinitionNames = new ArrayList(); + private final List beanDefinitionNames = new ArrayList(64); /** Whether bean definition metadata may be cached for all beans */ private boolean configurationFrozen = false; @@ -261,7 +261,7 @@ public T getBean(Class requiredType) throws BeansException { if (beanNames.length > 1) { ArrayList autowireCandidates = new ArrayList(); for (String beanName : beanNames) { - if (getBeanDefinition(beanName).isAutowireCandidate()) { + if (!containsBeanDefinition(beanName) || getBeanDefinition(beanName).isAutowireCandidate()) { autowireCandidates.add(beanName); } } @@ -323,7 +323,7 @@ public String[] getBeanNamesForType(Class type) { } public String[] getBeanNamesForType(Class type, boolean includeNonSingletons, boolean allowEagerInit) { - if (!isConfigurationFrozen() || type == null || !allowEagerInit) { + if (!isConfigurationFrozen() || type == null || !allowEagerInit) { return doGetBeanNamesForType(type, includeNonSingletons, allowEagerInit); } Map, String[]> cache = @@ -333,7 +333,9 @@ public String[] getBeanNamesForType(Class type, boolean includeNonSingletons, return resolvedBeanNames; } resolvedBeanNames = doGetBeanNamesForType(type, includeNonSingletons, allowEagerInit); - cache.put(type, resolvedBeanNames); + if (ClassUtils.isCacheSafe(type, getBeanClassLoader())) { + cache.put(type, resolvedBeanNames); + } return resolvedBeanNames; } @@ -460,12 +462,15 @@ public Map getBeansOfType(Class type, boolean includeNonSingle } public Map getBeansWithAnnotation(Class annotationType) { - Set beanNames = new LinkedHashSet(getBeanDefinitionCount()); - beanNames.addAll(Arrays.asList(getBeanDefinitionNames())); - beanNames.addAll(Arrays.asList(getSingletonNames())); Map results = new LinkedHashMap(); - for (String beanName : beanNames) { - if (findAnnotationOnBean(beanName, annotationType) != null) { + for (String beanName : getBeanDefinitionNames()) { + BeanDefinition beanDefinition = getBeanDefinition(beanName); + if (!beanDefinition.isAbstract() && findAnnotationOnBean(beanName, annotationType) != null) { + results.put(beanName, getBean(beanName)); + } + } + for (String beanName : getSingletonNames()) { + if (!results.containsKey(beanName) && findAnnotationOnBean(beanName, annotationType) != null) { results.put(beanName, getBean(beanName)); } } @@ -478,7 +483,9 @@ public Map getBeansWithAnnotation(Class an * found on the given class itself, as well as checking its raw bean class * if not found on the exposed bean reference (e.g. in case of a proxy). */ - public A findAnnotationOnBean(String beanName, Class annotationType) { + public A findAnnotationOnBean(String beanName, Class annotationType) + throws NoSuchBeanDefinitionException{ + A ann = null; Class beanType = getType(beanName); if (beanType != null) { @@ -502,10 +509,12 @@ public A findAnnotationOnBean(String beanName, Class a //--------------------------------------------------------------------- public void registerResolvableDependency(Class dependencyType, Object autowiredValue) { - Assert.notNull(dependencyType, "Type must not be null"); + Assert.notNull(dependencyType, "Dependency type must not be null"); if (autowiredValue != null) { - Assert.isTrue((autowiredValue instanceof ObjectFactory || dependencyType.isInstance(autowiredValue)), - "Value [" + autowiredValue + "] does not implement specified type [" + dependencyType.getName() + "]"); + if (!(autowiredValue instanceof ObjectFactory || dependencyType.isInstance(autowiredValue))) { + throw new IllegalArgumentException("Value [" + autowiredValue + + "] does not implement specified dependency type [" + dependencyType.getName() + "]"); + } this.resolvableDependencies.put(dependencyType, autowiredValue); } } @@ -595,12 +604,15 @@ public void preInstantiateSingletons() throws BeansException { if (this.logger.isInfoEnabled()) { this.logger.info("Pre-instantiating singletons in " + this); } + List beanNames; synchronized (this.beanDefinitionMap) { // Iterate over a copy to allow for init methods which in turn register new bean definitions. // While this may not be part of the regular factory bootstrap, it does otherwise work fine. beanNames = new ArrayList(this.beanDefinitionNames); } + + // Trigger initialization of all non-lazy singleton beans... for (String beanName : beanNames) { RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { @@ -650,8 +662,10 @@ public void registerBeanDefinition(String beanName, BeanDefinition beanDefinitio } } + BeanDefinition oldBeanDefinition; + synchronized (this.beanDefinitionMap) { - Object oldBeanDefinition = this.beanDefinitionMap.get(beanName); + oldBeanDefinition = this.beanDefinitionMap.get(beanName); if (oldBeanDefinition != null) { if (!this.allowBeanDefinitionOverriding) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, @@ -672,7 +686,9 @@ public void registerBeanDefinition(String beanName, BeanDefinition beanDefinitio this.beanDefinitionMap.put(beanName, beanDefinition); } - resetBeanDefinition(beanName); + if (oldBeanDefinition != null || containsSingleton(beanName)) { + resetBeanDefinition(beanName); + } } public void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException { @@ -707,9 +723,6 @@ protected void resetBeanDefinition(String beanName) { // (e.g. the default StaticMessageSource in a StaticApplicationContext). destroySingleton(beanName); - // Remove any assumptions about by-type mappings. - clearByTypeCache(); - // Reset all bean definitions that have the given bean as parent (recursively). for (String bdName : this.beanDefinitionNames) { if (!beanName.equals(bdName)) { 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 cbf7c872ff..99bf3c3cc0 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-2012 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -84,7 +84,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements private final Map singletonObjects = new ConcurrentHashMap(64); /** Cache of singleton factories: bean name --> ObjectFactory */ - private final Map singletonFactories = new HashMap(16); + private final Map> singletonFactories = new HashMap>(16); /** Cache of early singleton objects: bean name --> bean instance */ private final Map earlySingletonObjects = new HashMap(16); @@ -181,7 +181,7 @@ protected Object getSingleton(String beanName, boolean allowEarlyReference) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { - ObjectFactory singletonFactory = this.singletonFactories.get(beanName); + ObjectFactory singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); @@ -201,7 +201,7 @@ protected Object getSingleton(String beanName, boolean allowEarlyReference) { * with, if necessary * @return the registered singleton object */ - public Object getSingleton(String beanName, ObjectFactory singletonFactory) { + public Object getSingleton(String beanName, ObjectFactory singletonFactory) { Assert.notNull(beanName, "'beanName' must not be null"); synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); @@ -317,7 +317,7 @@ public boolean isSingletonCurrentlyInCreation(String beanName) { /** * Callback before singleton creation. - *

Default implementation register the singleton as currently in creation. + *

The default implementation register the singleton as currently in creation. * @param beanName the name of the singleton about to be created * @see #isSingletonCurrentlyInCreation */ @@ -539,13 +539,13 @@ protected void destroyBean(String beanName, DisposableBean bean) { } /** - * Expose the singleton mutex to subclasses. + * Exposes the singleton mutex to subclasses and external collaborators. *

Subclasses should synchronize on the given Object if they perform * any sort of extended singleton creation phase. In particular, subclasses * should not have their own mutexes involved in singleton creation, * to avoid the potential for deadlocks in lazy-init situations. */ - protected final Object getSingletonMutex() { + public final Object getSingletonMutex() { return this.singletonObjects; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java index 6a498344a9..ae394ca7c8 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,11 +65,12 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { private static final Log logger = LogFactory.getLog(DisposableBeanAdapter.class); - private static Class closeableInterface; + private static Class closeableInterface; static { try { - closeableInterface = DisposableBeanAdapter.class.getClassLoader().loadClass("java.lang.AutoCloseable"); + closeableInterface = ClassUtils.forName("java.lang.AutoCloseable", + DisposableBeanAdapter.class.getClassLoader()); } catch (ClassNotFoundException ex) { closeableInterface = Closeable.class; @@ -85,14 +86,14 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { private final boolean nonPublicAccessAllowed; + private final AccessControlContext acc; + private String destroyMethodName; private transient Method destroyMethod; private List beanPostProcessors; - private final AccessControlContext acc; - /** * Create a new DisposableBeanAdapter for the given bean. @@ -149,9 +150,9 @@ private DisposableBeanAdapter(Object bean, String beanName, boolean invokeDispos this.beanName = beanName; this.invokeDisposableBean = invokeDisposableBean; this.nonPublicAccessAllowed = nonPublicAccessAllowed; + this.acc = null; this.destroyMethodName = destroyMethodName; this.beanPostProcessors = postProcessors; - this.acc = null; } 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 71a22243f7..c5239b5aa4 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-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,11 +52,11 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg * @return the FactoryBean's object type, * or {@code null} if the type cannot be determined yet */ - protected Class getTypeForFactoryBean(final FactoryBean factoryBean) { + protected Class getTypeForFactoryBean(final FactoryBean factoryBean) { try { if (System.getSecurityManager() != null) { - return AccessController.doPrivileged(new PrivilegedAction() { - public Class run() { + return AccessController.doPrivileged(new PrivilegedAction>() { + public Class run() { return factoryBean.getObjectType(); } }, getAccessControlContext()); @@ -89,24 +89,50 @@ protected Object getCachedObjectForFactoryBean(String beanName) { * Obtain an object to expose from the given FactoryBean. * @param factory the FactoryBean instance * @param beanName the name of the bean - * @param shouldPostProcess whether the bean is subject for post-processing + * @param shouldPostProcess whether the bean is subject to post-processing * @return the object obtained from the FactoryBean * @throws BeanCreationException if FactoryBean object creation failed * @see org.springframework.beans.factory.FactoryBean#getObject() */ - protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName, boolean shouldPostProcess) { + protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName, boolean shouldPostProcess) { if (factory.isSingleton() && containsSingleton(beanName)) { synchronized (getSingletonMutex()) { Object object = this.factoryBeanObjectCache.get(beanName); if (object == null) { - object = doGetObjectFromFactoryBean(factory, beanName, shouldPostProcess); - this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT)); + object = doGetObjectFromFactoryBean(factory, beanName); + // Only post-process and store if not put there already during getObject() call above + // (e.g. because of circular reference processing triggered by custom getBean calls) + Object alreadyThere = this.factoryBeanObjectCache.get(beanName); + if (alreadyThere != null) { + object = alreadyThere; + } + else { + if (object != null && shouldPostProcess) { + try { + object = postProcessObjectFromFactoryBean(object, beanName); + } + catch (Throwable ex) { + throw new BeanCreationException(beanName, + "Post-processing of FactoryBean's singleton object failed", ex); + } + } + this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT)); + } } return (object != NULL_OBJECT ? object : null); } } else { - return doGetObjectFromFactoryBean(factory, beanName, shouldPostProcess); + Object object = doGetObjectFromFactoryBean(factory, beanName); + if (object != null && shouldPostProcess) { + try { + object = postProcessObjectFromFactoryBean(object, beanName); + } + catch (Throwable ex) { + throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex); + } + } + return object; } } @@ -114,13 +140,11 @@ protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName, * Obtain an object to expose from the given FactoryBean. * @param factory the FactoryBean instance * @param beanName the name of the bean - * @param shouldPostProcess whether the bean is subject for post-processing * @return the object obtained from the FactoryBean * @throws BeanCreationException if FactoryBean object creation failed * @see org.springframework.beans.factory.FactoryBean#getObject() */ - private Object doGetObjectFromFactoryBean( - final FactoryBean factory, final String beanName, final boolean shouldPostProcess) + private Object doGetObjectFromFactoryBean(final FactoryBean factory, final String beanName) throws BeanCreationException { Object object; @@ -149,23 +173,12 @@ public Object run() throws Exception { throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex); } - // Do not accept a null value for a FactoryBean that's not fully // initialized yet: Many FactoryBeans just return null then. if (object == null && isSingletonCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException( beanName, "FactoryBean which is currently in creation returned null from getObject"); } - - if (object != null && shouldPostProcess) { - try { - object = postProcessObjectFromFactoryBean(object, beanName); - } - catch (Throwable ex) { - throw new BeanCreationException(beanName, "Post-processing of the FactoryBean's object failed", ex); - } - } - return object; } @@ -190,12 +203,12 @@ protected Object postProcessObjectFromFactoryBean(Object object, String beanName * @return the bean instance as FactoryBean * @throws BeansException if the given bean cannot be exposed as a FactoryBean */ - protected FactoryBean getFactoryBean(String beanName, Object beanInstance) throws BeansException { + protected FactoryBean getFactoryBean(String beanName, Object beanInstance) throws BeansException { if (!(beanInstance instanceof FactoryBean)) { throw new BeanCreationException(beanName, "Bean instance of type [" + beanInstance.getClass() + "] is not a FactoryBean"); } - return (FactoryBean) beanInstance; + return (FactoryBean) beanInstance; } /** 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 82fddd2106..b8f0370cf3 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-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -87,7 +87,12 @@ public boolean equals(Object other) { @Override public String toString() { - return "Generic bean: " + super.toString(); + StringBuilder sb = new StringBuilder("Generic bean"); + if (this.parentName != null) { + sb.append(" with parent '").append(this.parentName).append("'"); + } + sb.append(": ").append(super.toString()); + return sb.toString(); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java index 560943d309..6aa560ce01 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,10 +37,9 @@ public class LookupOverride extends MethodOverride { /** * Construct a new LookupOverride. - * @param methodName the name of the method to override. - * This method must have no arguments. - * @param beanName name of the bean in the current BeanFactory - * that the overriden method should return + * @param methodName the name of the method to override + * @param beanName the name of the bean in the current BeanFactory + * that the overridden method should return */ public LookupOverride(String methodName, String beanName) { super(methodName); @@ -48,6 +47,7 @@ public LookupOverride(String methodName, String beanName) { this.beanName = beanName; } + /** * Return the name of the bean that should be returned by this method. */ @@ -55,9 +55,8 @@ public String getBeanName() { return this.beanName; } - /** - * Match method of the given name, with no parameters. + * Match the method of the given name, with no parameters. */ @Override public boolean matches(Method method) { @@ -65,11 +64,6 @@ public boolean matches(Method method) { } - @Override - public String toString() { - return "LookupOverride for method '" + getMethodName() + "'; will return bean '" + this.beanName + "'"; - } - @Override public boolean equals(Object other) { return (other instanceof LookupOverride && super.equals(other) && @@ -81,4 +75,9 @@ public int hashCode() { return (29 * super.hashCode() + ObjectUtils.nullSafeHashCode(this.beanName)); } + @Override + public String toString() { + return "LookupOverride for method '" + getMethodName() + "'; will return bean '" + this.beanName + "'"; + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/MethodOverride.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/MethodOverride.java index ecc984a7ce..33f969db4f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/MethodOverride.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/MethodOverride.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,14 +23,15 @@ import org.springframework.util.ObjectUtils; /** - * Object representing the override of a method on a managed - * object by the IoC container. + * Object representing the override of a method on a managed object by the IoC + * container. * - *

Note that the override mechanism is not intended as a - * generic means of inserting crosscutting code: use AOP for that. + *

Note that the override mechanism is not intended as a generic + * means of inserting crosscutting code: use AOP for that. * * @author Rod Johnson * @author Juergen Hoeller + * @author Sam Brannen * @since 1.1 */ public abstract class MethodOverride implements BeanMetadataElement { @@ -51,6 +52,7 @@ protected MethodOverride(String methodName) { this.methodName = methodName; } + /** * Return the name of the method to be overridden. */ @@ -59,17 +61,18 @@ public String getMethodName() { } /** - * Set whether the overridden method has to be considered as overloaded - * (that is, whether arg type matching has to happen). - *

Default is "true"; can be switched to "false" to optimize runtime performance. + * Set whether the overridden method is overloaded (i.e., whether argument + * type matching needs to occur to disambiguate methods of the same name). + *

Default is {@code true}; can be switched to {@code false} to optimize + * runtime performance. */ protected void setOverloaded(boolean overloaded) { this.overloaded = overloaded; } /** - * Return whether the overridden method has to be considered as overloaded - * (that is, whether arg type matching has to happen). + * Return whether the overridden method is overloaded (i.e., whether argument + * type matching needs to occur to disambiguate methods of the same name). */ protected boolean isOverloaded() { return this.overloaded; @@ -87,11 +90,10 @@ public Object getSource() { return this.source; } - /** - * Subclasses must override this to indicate whether they match - * the given method. This allows for argument list checking - * as well as method name checking. + * Subclasses must override this to indicate whether they match the + * given method. This allows for argument list checking as well as method + * name checking. * @param method the method to check * @return whether this override matches the given method */ @@ -108,7 +110,6 @@ public boolean equals(Object other) { } MethodOverride that = (MethodOverride) other; return (ObjectUtils.nullSafeEquals(this.methodName, that.methodName) && - this.overloaded == that.overloaded && ObjectUtils.nullSafeEquals(this.source, that.source)); } @@ -116,7 +117,6 @@ public boolean equals(Object other) { public int hashCode() { int hashCode = ObjectUtils.nullSafeHashCode(this.methodName); hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.source); - hashCode = 29 * hashCode + (this.overloaded ? 1 : 0); return hashCode; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ReplaceOverride.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ReplaceOverride.java index e570a24c57..55ef696b59 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ReplaceOverride.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ReplaceOverride.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,6 +52,7 @@ public ReplaceOverride(String methodName, String methodReplacerBeanName) { this.methodReplacerBeanName = methodReplacerBeanName; } + /** * Return the name of the bean implementing MethodReplacer. */ @@ -68,28 +69,22 @@ public void addTypeIdentifier(String identifier) { this.typeIdentifiers.add(identifier); } - @Override public boolean matches(Method method) { - // TODO could cache result for efficiency if (!method.getName().equals(getMethodName())) { - // It can't match. return false; } - if (!isOverloaded()) { - // No overloaded: don't worry about arg type matching. + // Not overloaded: don't worry about arg type matching... return true; } - - // If we get to here, we need to insist on precise argument matching. + // If we get here, we need to insist on precise argument matching... if (this.typeIdentifiers.size() != method.getParameterTypes().length) { return false; } for (int i = 0; i < this.typeIdentifiers.size(); i++) { String identifier = this.typeIdentifiers.get(i); if (!method.getParameterTypes()[i].getName().contains(identifier)) { - // This parameter cannot match. return false; } } @@ -97,12 +92,6 @@ public boolean matches(Method method) { } - @Override - public String toString() { - return "Replace override for method '" + getMethodName() + "; will call bean '" + - this.methodReplacerBeanName + "'"; - } - @Override public boolean equals(Object other) { if (!(other instanceof ReplaceOverride) || !super.equals(other)) { @@ -121,4 +110,10 @@ public int hashCode() { return hashCode; } + @Override + public String toString() { + return "Replace override for method '" + getMethodName() + "; will call bean '" + + this.methodReplacerBeanName + "'"; + } + } 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 64d0555d49..c209e49ff8 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 @@ -18,8 +18,8 @@ import java.lang.reflect.Member; import java.lang.reflect.Method; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.HashSet; +import java.util.Set; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.config.BeanDefinition; @@ -48,19 +48,10 @@ @SuppressWarnings("serial") public class RootBeanDefinition extends AbstractBeanDefinition { - // using a ConcurrentHashMap as a Set - private final Map externallyManagedConfigMembers = new ConcurrentHashMap(0); - - // using a ConcurrentHashMap as a Set - private final Map externallyManagedInitMethods = new ConcurrentHashMap(0); - - // using a ConcurrentHashMap as a Set - private final Map externallyManagedDestroyMethods = new ConcurrentHashMap(0); + boolean allowCaching = true; private BeanDefinitionHolder decoratedDefinition; - boolean allowCaching = true; - private volatile Class targetType; boolean isFactoryMethodUnique = false; @@ -87,6 +78,12 @@ public class RootBeanDefinition extends AbstractBeanDefinition { /** Package-visible field that indicates a before-instantiation post-processor having kicked in */ volatile Boolean beforeInstantiationResolved; + private Set externallyManagedConfigMembers; + + private Set externallyManagedInitMethods; + + private Set externallyManagedDestroyMethods; + /** * Create a new RootBeanDefinition, to be configured through its bean @@ -107,7 +104,7 @@ public RootBeanDefinition() { * Create a new RootBeanDefinition for a singleton. * @param beanClass the class of the bean to instantiate */ - public RootBeanDefinition(Class beanClass) { + public RootBeanDefinition(Class beanClass) { super(); setBeanClass(beanClass); } @@ -147,7 +144,7 @@ public RootBeanDefinition(Class beanClass, int autowireMode) { * @param dependencyCheck whether to perform a dependency check for objects * (not applicable to autowiring a constructor, thus ignored there) */ - public RootBeanDefinition(Class beanClass, int autowireMode, boolean dependencyCheck) { + public RootBeanDefinition(Class beanClass, int autowireMode, boolean dependencyCheck) { super(); setBeanClass(beanClass); setAutowireMode(autowireMode); @@ -191,7 +188,7 @@ public RootBeanDefinition(Class beanClass, MutablePropertyValues pvs, boolean si * @param cargs the constructor argument values to apply * @param pvs the property values to apply */ - public RootBeanDefinition(Class beanClass, ConstructorArgumentValues cargs, MutablePropertyValues pvs) { + public RootBeanDefinition(Class beanClass, ConstructorArgumentValues cargs, MutablePropertyValues pvs) { super(cargs, pvs); setBeanClass(beanClass); } @@ -225,7 +222,11 @@ public RootBeanDefinition(String beanClassName, ConstructorArgumentValues cargs, * @param original the original bean definition to copy from */ public RootBeanDefinition(RootBeanDefinition original) { - this((BeanDefinition) original); + super((BeanDefinition) original); + this.allowCaching = original.allowCaching; + this.decoratedDefinition = original.decoratedDefinition; + this.targetType = original.targetType; + this.isFactoryMethodUnique = original.isFactoryMethodUnique; } /** @@ -235,13 +236,6 @@ public RootBeanDefinition(RootBeanDefinition original) { */ RootBeanDefinition(BeanDefinition original) { super(original); - if (original instanceof RootBeanDefinition) { - RootBeanDefinition originalRbd = (RootBeanDefinition) original; - this.decoratedDefinition = originalRbd.decoratedDefinition; - this.allowCaching = originalRbd.allowCaching; - this.targetType = originalRbd.targetType; - this.isFactoryMethodUnique = originalRbd.isFactoryMethodUnique; - } } @@ -255,6 +249,20 @@ public void setParentName(String parentName) { } } + /** + * Register a target definition that is being decorated by this bean definition. + */ + public void setDecoratedDefinition(BeanDefinitionHolder decoratedDefinition) { + this.decoratedDefinition = decoratedDefinition; + } + + /** + * Return the target definition that is being decorated by this bean definition, if any. + */ + public BeanDefinitionHolder getDecoratedDefinition() { + return this.decoratedDefinition; + } + /** * Specify the target type of this bean definition, if known in advance. */ @@ -297,37 +305,52 @@ public Method getResolvedFactoryMethod() { } } - public void registerExternallyManagedConfigMember(Member configMember) { - this.externallyManagedConfigMembers.put(configMember, Boolean.TRUE); + synchronized (this.postProcessingLock) { + if (this.externallyManagedConfigMembers == null) { + this.externallyManagedConfigMembers = new HashSet(1); + } + this.externallyManagedConfigMembers.add(configMember); + } } public boolean isExternallyManagedConfigMember(Member configMember) { - return this.externallyManagedConfigMembers.containsKey(configMember); + synchronized (this.postProcessingLock) { + return (this.externallyManagedConfigMembers != null && + this.externallyManagedConfigMembers.contains(configMember)); + } } public void registerExternallyManagedInitMethod(String initMethod) { - this.externallyManagedInitMethods.put(initMethod, Boolean.TRUE); + synchronized (this.postProcessingLock) { + if (this.externallyManagedInitMethods == null) { + this.externallyManagedInitMethods = new HashSet(1); + } + this.externallyManagedInitMethods.add(initMethod); + } } public boolean isExternallyManagedInitMethod(String initMethod) { - return this.externallyManagedInitMethods.containsKey(initMethod); + synchronized (this.postProcessingLock) { + return (this.externallyManagedInitMethods != null && + this.externallyManagedInitMethods.contains(initMethod)); + } } public void registerExternallyManagedDestroyMethod(String destroyMethod) { - this.externallyManagedDestroyMethods.put(destroyMethod, Boolean.TRUE); + synchronized (this.postProcessingLock) { + if (this.externallyManagedDestroyMethods == null) { + this.externallyManagedDestroyMethods = new HashSet(1); + } + this.externallyManagedDestroyMethods.add(destroyMethod); + } } public boolean isExternallyManagedDestroyMethod(String destroyMethod) { - return this.externallyManagedDestroyMethods.containsKey(destroyMethod); - } - - public void setDecoratedDefinition(BeanDefinitionHolder decoratedDefinition) { - this.decoratedDefinition = decoratedDefinition; - } - - public BeanDefinitionHolder getDecoratedDefinition() { - return this.decoratedDefinition; + synchronized (this.postProcessingLock) { + return (this.externallyManagedDestroyMethods != null && + this.externallyManagedDestroyMethods.contains(destroyMethod)); + } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java index b06904221f..cdc12a1dbf 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java @@ -62,14 +62,14 @@ public Object instantiate(RootBeanDefinition beanDefinition, String beanName, Be synchronized (beanDefinition.constructorArgumentLock) { constructorToUse = (Constructor) beanDefinition.resolvedConstructorOrFactoryMethod; if (constructorToUse == null) { - final Class clazz = beanDefinition.getBeanClass(); + final Class clazz = beanDefinition.getBeanClass(); if (clazz.isInterface()) { throw new BeanInstantiationException(clazz, "Specified class is an interface"); } try { if (System.getSecurityManager() != null) { constructorToUse = AccessController.doPrivileged(new PrivilegedExceptionAction() { - public Constructor run() throws Exception { + public Constructor run() throws Exception { return clazz.getDeclaredConstructor((Class[]) null); } }); @@ -132,7 +132,7 @@ public Object run() { * Instantiation should use the given constructor and parameters. */ protected Object instantiateWithMethodInjection(RootBeanDefinition beanDefinition, - String beanName, BeanFactory owner, Constructor ctor, Object[] args) { + String beanName, BeanFactory owner, Constructor ctor, Object[] args) { throw new UnsupportedOperationException( "Method Injection not supported in SimpleInstantiationStrategy"); 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 4eef1b4751..16681cc4f3 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-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -93,7 +93,7 @@ public Object getBean(String name) throws BeansException { if (bean instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(name)) { try { - return ((FactoryBean) bean).getObject(); + return ((FactoryBean) bean).getObject(); } catch (Exception ex) { throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex); @@ -129,7 +129,7 @@ else if (beanNames.length > 1) { public Object getBean(String name, Object... args) throws BeansException { if (args != null) { throw new UnsupportedOperationException( - "StaticListableBeanFactory does not support explicit bean creation arguments)"); + "StaticListableBeanFactory does not support explicit bean creation arguments"); } return getBean(name); } @@ -141,18 +141,18 @@ public boolean containsBean(String name) { public boolean isSingleton(String name) throws NoSuchBeanDefinitionException { Object bean = getBean(name); // In case of FactoryBean, return singleton status of created object. - return (bean instanceof FactoryBean && ((FactoryBean) bean).isSingleton()); + return (bean instanceof FactoryBean && ((FactoryBean) bean).isSingleton()); } public boolean isPrototype(String name) throws NoSuchBeanDefinitionException { Object bean = getBean(name); // In case of FactoryBean, return prototype status of created object. - return ((bean instanceof SmartFactoryBean && ((SmartFactoryBean) bean).isPrototype()) || - (bean instanceof FactoryBean && !((FactoryBean) bean).isSingleton())); + return ((bean instanceof SmartFactoryBean && ((SmartFactoryBean) bean).isPrototype()) || + (bean instanceof FactoryBean && !((FactoryBean) bean).isSingleton())); } - public boolean isTypeMatch(String name, Class targetType) throws NoSuchBeanDefinitionException { - Class type = getType(name); + public boolean isTypeMatch(String name, Class targetType) throws NoSuchBeanDefinitionException { + Class type = getType(name); return (targetType == null || (type != null && targetType.isAssignableFrom(type))); } @@ -167,7 +167,7 @@ public Class getType(String name) throws NoSuchBeanDefinitionException { if (bean instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(name)) { // If it's a FactoryBean, we want to look at what it creates, not the factory class. - return ((FactoryBean) bean).getObjectType(); + return ((FactoryBean) bean).getObjectType(); } return bean.getClass(); } @@ -193,18 +193,18 @@ public String[] getBeanDefinitionNames() { return StringUtils.toStringArray(this.beans.keySet()); } - public String[] getBeanNamesForType(Class type) { + public String[] getBeanNamesForType(Class type) { return getBeanNamesForType(type, true, true); } - public String[] getBeanNamesForType(Class type, boolean includeNonSingletons, boolean includeFactoryBeans) { + public String[] getBeanNamesForType(Class type, boolean includeNonSingletons, boolean includeFactoryBeans) { boolean isFactoryType = (type != null && FactoryBean.class.isAssignableFrom(type)); List matches = new ArrayList(); for (String name : this.beans.keySet()) { Object beanInstance = this.beans.get(name); if (beanInstance instanceof FactoryBean && !isFactoryType) { if (includeFactoryBeans) { - Class objectType = ((FactoryBean) beanInstance).getObjectType(); + Class objectType = ((FactoryBean) beanInstance).getObjectType(); if (objectType != null && (type == null || type.isAssignableFrom(objectType))) { matches.add(name); } @@ -237,8 +237,8 @@ public Map getBeansOfType(Class type, boolean includeNonSingle if (beanInstance instanceof FactoryBean && !isFactoryType) { if (includeFactoryBeans) { // Match object created by FactoryBean. - FactoryBean factory = (FactoryBean) beanInstance; - Class objectType = factory.getObjectType(); + FactoryBean factory = (FactoryBean) beanInstance; + Class objectType = factory.getObjectType(); if ((includeNonSingletons || factory.isSingleton()) && objectType != null && (type == null || type.isAssignableFrom(objectType))) { matches.put(beanName, getBean(beanName, type)); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/AbstractSimpleBeanDefinitionParser.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/AbstractSimpleBeanDefinitionParser.java index 0bfcb375da..70adeede2b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/AbstractSimpleBeanDefinitionParser.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/AbstractSimpleBeanDefinitionParser.java @@ -149,7 +149,7 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit */ protected boolean isEligibleAttribute(Attr attribute, ParserContext parserContext) { boolean eligible = isEligibleAttribute(attribute); - if(!eligible) { + if (!eligible) { String fullName = attribute.getName(); eligible = (!fullName.equals("xmlns") && !fullName.startsWith("xmlns:") && isEligibleAttribute(parserContext.getDelegate().getLocalName(attribute))); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionDocumentReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionDocumentReader.java index ffe9d294d9..a41138fd36 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionDocumentReader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionDocumentReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author 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,9 @@ public interface BeanDefinitionDocumentReader { * Set the Environment to use when reading bean definitions. *

Used for evaluating profile information to determine whether a * {@code } document/element should be included or ignored. + * @deprecated in favor of Environment access via XmlReaderContext */ + @Deprecated void setEnvironment(Environment environment); /** 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 de4c3e695e..6be94c1a89 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-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,7 +59,6 @@ import org.springframework.beans.factory.support.MethodOverrides; import org.springframework.beans.factory.support.ReplaceOverride; import org.springframework.core.env.Environment; -import org.springframework.core.env.StandardEnvironment; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; @@ -246,12 +245,12 @@ public class BeanDefinitionParserDelegate { private final XmlReaderContext readerContext; + private final Environment environment; + private final DocumentDefaultsDefinition defaults = new DocumentDefaultsDefinition(); private final ParseState parseState = new ParseState(); - private Environment environment; - /** * Stores all used bean names so we can enforce uniqueness on a per * beans-element basis. Duplicate bean ids/names may not exist within the @@ -261,26 +260,24 @@ public class BeanDefinitionParserDelegate { /** - * Create a new BeanDefinitionParserDelegate associated with the - * supplied {@link XmlReaderContext} and {@link Environment}. + * Create a new BeanDefinitionParserDelegate associated with the supplied + * {@link XmlReaderContext}. + */ + public BeanDefinitionParserDelegate(XmlReaderContext readerContext) { + this(readerContext, readerContext.getReader().getEnvironment()); + } + + /** + * Create a new BeanDefinitionParserDelegate associated with the supplied + * {@link XmlReaderContext}. */ public BeanDefinitionParserDelegate(XmlReaderContext readerContext, Environment environment) { Assert.notNull(readerContext, "XmlReaderContext must not be null"); - Assert.notNull(readerContext, "Environment must not be null"); + Assert.notNull(environment, "Environment must not be null"); this.readerContext = readerContext; this.environment = environment; } - /** - * Create a new BeanDefinitionParserDelegate associated with the - * supplied {@link XmlReaderContext} and a new {@link StandardEnvironment}. - * @deprecated since Spring 3.1 in favor of - * {@link #BeanDefinitionParserDelegate(XmlReaderContext, Environment)} - */ - @Deprecated - public BeanDefinitionParserDelegate(XmlReaderContext readerContext) { - this(readerContext, new StandardEnvironment()); - } /** * Get the {@link XmlReaderContext} associated with this helper instance. @@ -326,6 +323,13 @@ protected void error(String message, Element source, Throwable cause) { } + /** + * Initialize the default settings assuming a {@code null} parent delegate. + */ + public void initDefaults(Element root) { + initDefaults(root, null); + } + /** * Initialize the default lazy-init, autowire, dependency check settings, * init-method, destroy-method and merge settings. Support nested 'beans' @@ -339,16 +343,6 @@ public void initDefaults(Element root, BeanDefinitionParserDelegate parent) { this.readerContext.fireDefaultsRegistered(this.defaults); } - /** - * Initialize the default settings assuming a {@code null} parent delegate. - * @deprecated in Spring 3.1 in favor of - * {@link #initDefaults(Element, BeanDefinitionParserDelegate)} - */ - @Deprecated - public void initDefaults(Element root) { - initDefaults(root, null); - } - /** * Populate the given DocumentDefaultsDefinition instance with the default lazy-init, * autowire, dependency check settings, init-method, destroy-method and merge settings. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java index a422d5b4fc..63bdfc3ec7 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author 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,6 @@ import org.springframework.core.env.Environment; import org.springframework.core.io.Resource; import org.springframework.core.io.support.ResourcePatternUtils; -import org.springframework.util.Assert; import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; @@ -84,19 +83,13 @@ public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocume private BeanDefinitionParserDelegate delegate; - /** - * {@inheritDoc} - *

Default value is {@code null}; property is required for parsing any - * {@code } element with a {@code profile} attribute present. - * @see #doRegisterBeanDefinitions - */ + @Deprecated public void setEnvironment(Environment environment) { this.environment = environment; } /** - * {@inheritDoc} - *

This implementation parses bean definitions according to the "spring-beans" XSD + * This implementation parses bean definitions according to the "spring-beans" XSD * (or DTD, historically). *

Opens a DOM Document; then initializes the default settings * specified at the {@code } level; then parses the contained bean definitions. @@ -108,20 +101,35 @@ public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext doRegisterBeanDefinitions(root); } + /** + * Return the descriptor for the XML resource that this parser works on. + */ + protected final XmlReaderContext getReaderContext() { + return this.readerContext; + } + + /** + * Invoke the {@link org.springframework.beans.factory.parsing.SourceExtractor} to pull the + * source metadata from the supplied {@link Element}. + */ + protected Object extractSource(Element ele) { + return getReaderContext().extractSource(ele); + } + + private Environment getEnvironment() { + return (this.environment != null ? this.environment : getReaderContext().getReader().getEnvironment()); + } + /** * Register each bean definition within the given root {@code } element. - * @throws IllegalStateException if {@code actualResources = new LinkedHashSet(4); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/SimpleConstructorNamespaceHandler.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/SimpleConstructorNamespaceHandler.java index 4110ed0a22..171de343a3 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/SimpleConstructorNamespaceHandler.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/SimpleConstructorNamespaceHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,20 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.beans.factory.xml; import java.util.Collection; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.ConstructorArgumentValues; -import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; +import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.core.Conventions; import org.springframework.util.StringUtils; -import org.w3c.dom.Attr; -import org.w3c.dom.Element; -import org.w3c.dom.Node; /** * Simple {@code NamespaceHandler} implementation that maps custom @@ -34,8 +36,7 @@ * that this {@code NamespaceHandler} does not have a corresponding schema * since there is no way to know in advance all possible attribute names. * - *

- * An example of the usage of this {@code NamespaceHandler} is shown below: + *

An example of the usage of this {@code NamespaceHandler} is shown below: * *

  * <bean id="author" class="..TestBean" c:name="Enescu" c:work-ref="compositions"/>
@@ -51,14 +52,17 @@
  * support for indexes or types. Further more, the names are used as hints by
  * the container which, by default, does type introspection.
  *
- * @see SimplePropertyNamespaceHandler
  * @author Costin Leau
+ * @since 3.1
+ * @see SimplePropertyNamespaceHandler
  */
 public class SimpleConstructorNamespaceHandler implements NamespaceHandler {
 
 	private static final String REF_SUFFIX = "-ref";
+
 	private static final String DELIMITER_PREFIX = "_";
 
+
 	public void init() {
 	}
 
@@ -99,7 +103,8 @@ public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition,
 					int index = -1;
 					try {
 						index = Integer.parseInt(arg);
-					} catch (NumberFormatException ex) {
+					}
+					catch (NumberFormatException ex) {
 						parserContext.getReaderContext().error(
 								"Constructor argument '" + argName + "' specifies an invalid integer", attr);
 					}
@@ -133,11 +138,8 @@ public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition,
 	}
 
 	private boolean containsArgWithName(String name, ConstructorArgumentValues cvs) {
-		if (!checkName(name, cvs.getGenericArgumentValues())) {
-			return checkName(name, cvs.getIndexedArgumentValues().values());
-		}
-
-		return true;
+		return (checkName(name, cvs.getGenericArgumentValues()) ||
+				checkName(name, cvs.getIndexedArgumentValues().values()));
 	}
 
 	private boolean checkName(String name, Collection values) {
@@ -148,4 +150,5 @@ private boolean checkName(String name, Collection values) {
 		}
 		return false;
 	}
+
 }
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 16b58500bc..860799adc8 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-2012 the original author or authors.
+ * Copyright 2002-2013 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -135,6 +135,7 @@ public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
 		super(registry);
 	}
 
+
 	/**
 	 * Set whether to use XML validation. Default is {@code true}.
 	 * 

This method switches namespace awareness on if validation is turned off, @@ -486,9 +487,10 @@ protected int detectValidationMode(Resource resource) { * @see #setDocumentReaderClass * @see BeanDefinitionDocumentReader#registerBeanDefinitions */ + @SuppressWarnings("deprecation") public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); - documentReader.setEnvironment(this.getEnvironment()); + documentReader.setEnvironment(getEnvironment()); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomBooleanEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomBooleanEditor.java index 985ba1723c..a27d708e0c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomBooleanEditor.java +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomBooleanEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -97,6 +97,7 @@ public CustomBooleanEditor(String trueString, String falseString, boolean allowE this.allowEmpty = allowEmpty; } + @Override public void setAsText(String text) throws IllegalArgumentException { String input = (text != null ? text.trim() : null); @@ -104,20 +105,20 @@ public void setAsText(String text) throws IllegalArgumentException { // Treat empty String as null value. setValue(null); } - else if (this.trueString != null && input.equalsIgnoreCase(this.trueString)) { + else if (this.trueString != null && this.trueString.equalsIgnoreCase(input)) { setValue(Boolean.TRUE); } - else if (this.falseString != null && input.equalsIgnoreCase(this.falseString)) { + else if (this.falseString != null && this.falseString.equalsIgnoreCase(input)) { setValue(Boolean.FALSE); } else if (this.trueString == null && - (input.equalsIgnoreCase(VALUE_TRUE) || input.equalsIgnoreCase(VALUE_ON) || - input.equalsIgnoreCase(VALUE_YES) || input.equals(VALUE_1))) { + (VALUE_TRUE.equalsIgnoreCase(input) || VALUE_ON.equalsIgnoreCase(input) || + VALUE_YES.equalsIgnoreCase(input) || VALUE_1.equals(input))) { setValue(Boolean.TRUE); } else if (this.falseString == null && - (input.equalsIgnoreCase(VALUE_FALSE) || input.equalsIgnoreCase(VALUE_OFF) || - input.equalsIgnoreCase(VALUE_NO) || input.equals(VALUE_0))) { + (VALUE_FALSE.equalsIgnoreCase(input) || VALUE_OFF.equalsIgnoreCase(input) || + VALUE_NO.equalsIgnoreCase(input) || VALUE_0.equals(input))) { setValue(Boolean.FALSE); } else { 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 24467400fa..7a827c9481 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-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,13 +16,13 @@ package org.springframework.beans.propertyeditors; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - import java.beans.PropertyEditorSupport; import java.util.Locale; import java.util.ResourceBundle; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + /** * {@link java.beans.PropertyEditor} implementation for * {@link java.util.ResourceBundle ResourceBundles}. @@ -87,7 +87,8 @@ public void setAsText(String text) throws IllegalArgumentException { int indexOfBaseNameSeparator = rawBaseName.indexOf(BASE_NAME_SEPARATOR); if (indexOfBaseNameSeparator == -1) { bundle = ResourceBundle.getBundle(rawBaseName); - } else { + } + else { // it potentially has locale information String baseName = rawBaseName.substring(0, indexOfBaseNameSeparator); if (!StringUtils.hasText(baseName)) { diff --git a/spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java b/spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java index 3f4abc6568..e2df19e3ca 100644 --- a/spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java +++ b/spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -78,9 +78,12 @@ public ResourceEditorRegistrar(ResourceLoader resourceLoader) { } /** - * Create a new ResourceEditorRegistrar for the given ResourceLoader + * Create a new ResourceEditorRegistrar for the given {@link ResourceLoader} + * and {@link PropertyResolver}. * @param resourceLoader the ResourceLoader (or ResourcePatternResolver) * to create editors for (usually an ApplicationContext) + * @param propertyResolver the PropertyResolver (usually an Environment) + * @see org.springframework.core.env.Environment * @see org.springframework.core.io.support.ResourcePatternResolver * @see org.springframework.context.ApplicationContext */ 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 b35e6aabf7..246421c634 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java @@ -16,8 +16,6 @@ package org.springframework.beans; -import static org.junit.Assert.*; - import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; @@ -25,6 +23,7 @@ import java.util.List; import org.junit.Test; + import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.propertyeditors.CustomDateEditor; import org.springframework.core.io.Resource; @@ -33,6 +32,7 @@ import org.springframework.tests.sample.beans.ITestBean; import org.springframework.tests.sample.beans.TestBean; +import static org.junit.Assert.*; /** * Unit tests for {@link BeanUtils}. @@ -79,8 +79,7 @@ public void testGetPropertyDescriptors() throws Exception { @Test public void testBeanPropertyIsArray() { PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(ContainerBean.class); - for (int i = 0; i < descriptors.length; i++) { - PropertyDescriptor descriptor = descriptors[i]; + for (PropertyDescriptor descriptor : descriptors) { if ("containedBeans".equals(descriptor.getName())) { assertTrue("Property should be an array", descriptor.getPropertyType().isArray()); assertEquals(descriptor.getPropertyType().getComponentType(), ContainedBean.class); @@ -171,7 +170,7 @@ public void testCopyPropertiesWithIgnore() throws Exception { assertTrue("Touchy empty", tb2.getTouchy() == null); // "spouse", "touchy", "age" should not be copied - BeanUtils.copyProperties(tb, tb2, new String[]{"spouse", "touchy", "age"}); + BeanUtils.copyProperties(tb, tb2, "spouse", "touchy", "age"); assertTrue("Name copied", tb2.getName() == null); assertTrue("Age still empty", tb2.getAge() == 0); assertTrue("Touchy still empty", tb2.getTouchy() == null); @@ -182,10 +181,23 @@ public void testCopyPropertiesWithIgnoredNonExistingProperty() { NameAndSpecialProperty source = new NameAndSpecialProperty(); source.setName("name"); TestBean target = new TestBean(); - BeanUtils.copyProperties(source, target, new String[]{"specialProperty"}); + BeanUtils.copyProperties(source, target, "specialProperty"); assertEquals(target.getName(), "name"); } + @Test + public void testCopyPropertiesWithInvalidProperty() { + InvalidProperty source = new InvalidProperty(); + source.setName("name"); + source.setFlag1(true); + source.setFlag2(true); + InvalidProperty target = new InvalidProperty(); + BeanUtils.copyProperties(source, target); + assertEquals(target.getName(), "name"); + assertTrue(target.getFlag1()); + assertTrue(target.getFlag2()); + } + @Test public void testResolveSimpleSignature() throws Exception { Method desiredMethod = MethodSignatureBean.class.getMethod("doSomething"); @@ -257,7 +269,8 @@ public void testSPR6063() { assertEquals(String.class, keyDescr.getPropertyType()); for (PropertyDescriptor propertyDescriptor : descrs) { if (propertyDescriptor.getName().equals(keyDescr.getName())) { - assertEquals(propertyDescriptor.getName() + " has unexpected type", keyDescr.getPropertyType(), propertyDescriptor.getPropertyType()); + assertEquals(propertyDescriptor.getName() + " has unexpected type", + keyDescr.getPropertyType(), propertyDescriptor.getPropertyType()); } } } @@ -292,6 +305,51 @@ public int getSpecialProperty() { } + @SuppressWarnings("unused") + private static class InvalidProperty { + + private String name; + + private String value; + + private boolean flag1; + + private boolean flag2; + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + public void setValue(int value) { + this.value = Integer.toString(value); + } + + public String getValue() { + return this.value; + } + + public void setFlag1(boolean flag1) { + this.flag1 = flag1; + } + + public Boolean getFlag1() { + return this.flag1; + } + + public void setFlag2(Boolean flag2) { + this.flag2 = flag2; + } + + public boolean getFlag2() { + return this.flag2; + } + } + + @SuppressWarnings("unused") private static class ContainerBean { @@ -347,6 +405,7 @@ public void doSomethingWithAMultiDimensionalArray(String[][] strings) { } } + private interface MapEntry { K getKey(); @@ -358,6 +417,7 @@ private interface MapEntry { void setValue(V value); } + private static class Bean implements MapEntry { private String key; diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java index 9ec9af033b..7533d39a7b 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author 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,13 +16,6 @@ package org.springframework.beans; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - import java.beans.PropertyEditorSupport; import java.math.BigDecimal; import java.math.BigInteger; @@ -44,11 +37,16 @@ import org.apache.commons.logging.LogFactory; import org.junit.Test; + import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.propertyeditors.CustomNumberEditor; import org.springframework.beans.propertyeditors.StringArrayPropertyEditor; import org.springframework.beans.propertyeditors.StringTrimmerEditor; import org.springframework.beans.support.DerivedFromProtectedBaseBean; +import org.springframework.core.convert.ConversionFailedException; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.core.convert.support.GenericConversionService; import org.springframework.tests.Assume; import org.springframework.tests.TestGroup; import org.springframework.tests.sample.beans.BooleanTestBean; @@ -56,13 +54,11 @@ import org.springframework.tests.sample.beans.IndexedTestBean; import org.springframework.tests.sample.beans.NumberTestBean; import org.springframework.tests.sample.beans.TestBean; -import org.springframework.core.convert.ConversionFailedException; -import org.springframework.core.convert.TypeDescriptor; -import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.core.convert.support.GenericConversionService; import org.springframework.util.StopWatch; import org.springframework.util.StringUtils; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; /** * @author Rod Johnson @@ -1553,14 +1549,55 @@ public void testWildcardedGenericEnum() { @Test public void cornerSpr10115() { Spr10115Bean foo = new Spr10115Bean(); - BeanWrapperImpl bwi = new BeanWrapperImpl(); - bwi.setWrappedInstance(foo); + BeanWrapperImpl bwi = new BeanWrapperImpl(foo); bwi.setPropertyValue("prop1", "val1"); assertEquals("val1", Spr10115Bean.prop1); } + @Test + public void testArrayToObject() { + ArrayToObject foo = new ArrayToObject(); + BeanWrapperImpl bwi = new BeanWrapperImpl(foo); + + Object[] array = new Object[] {"1","2"}; + bwi.setPropertyValue("object", array); + assertThat(foo.getObject(), equalTo((Object) array)); + + array = new Object[] {"1"}; + bwi.setPropertyValue("object", array); + assertThat(foo.getObject(), equalTo((Object) array)); + } + + @Test + public void testPropertyTypeMismatch() { + PropertyTypeMismatch foo = new PropertyTypeMismatch(); + BeanWrapperImpl bwi = new BeanWrapperImpl(foo); + bwi.setPropertyValue("object", "a String"); + assertEquals("a String", foo.value); + assertEquals(8, bwi.getPropertyValue("object")); + } + + @Test + public void testGenericArraySetter() { + SkipReaderStub foo = new SkipReaderStub(); + BeanWrapperImpl bwi = new BeanWrapperImpl(foo); + List values = new LinkedList(); + values.add("1"); + values.add("2"); + values.add("3"); + values.add("4"); + bwi.setPropertyValue("items", values); + Object[] result = foo.items; + assertEquals(4, result.length); + assertEquals("1", result[0]); + assertEquals("2", result[1]); + assertEquals("3", result[2]); + assertEquals("4", result[3]); + } + static class Spr10115Bean { + private static String prop1; public static void setProp1(String prop1) { @@ -1944,4 +1981,49 @@ public enum TestEnum { TEST_VALUE } + + public static class ArrayToObject { + + private Object object; + + public void setObject(Object object) { + this.object = object; + } + + public Object getObject() { + return object; + } + } + + + public static class PropertyTypeMismatch { + + public String value; + + public void setObject(String object) { + this.value = object; + } + + public Integer getObject() { + return (this.value != null ? this.value.length() : null); + } + } + + + public static class SkipReaderStub { + + public T[] items; + + public SkipReaderStub() { + } + + public SkipReaderStub(T... items) { + this.items = items; + } + + public void setItems(T... items) { + this.items = items; + } + } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java b/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java index 30b2013d07..6d235ab84f 100644 --- a/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author 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,29 +21,19 @@ import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; - -import java.lang.reflect.Method; import java.math.BigDecimal; import org.junit.Test; import org.springframework.core.JdkVersion; import org.springframework.tests.sample.beans.TestBean; -import org.springframework.util.ClassUtils; - - -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.lessThan; - import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; - +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; /** - * Unit tests for {@link ExtendedBeanInfo}. - * * @author Chris Beams * @since 3.1 */ @@ -207,10 +197,6 @@ public Integer getProperty1() { } } - interface Spr9453 { - T getProp(); - } - @Test public void cornerSpr9453() throws IntrospectionException { final class Bean implements Spr9453> { @@ -335,10 +321,8 @@ public void setFoo(Integer foo) { } BeanInfo ebi = new ExtendedBeanInfo(bi); assertThat(hasReadMethodForProperty(bi, "foo"), is(true)); - assertThat(hasWriteMethodForProperty(bi, "foo"), is(false)); - assertThat(hasReadMethodForProperty(ebi, "foo"), is(true)); - assertThat(hasWriteMethodForProperty(ebi, "foo"), is(false)); + assertEquals(hasWriteMethodForProperty(bi, "foo"), hasWriteMethodForProperty(ebi, "foo")); } @Test @@ -352,10 +336,8 @@ public void setFoos(int index, Integer foo) { } BeanInfo ebi = new ExtendedBeanInfo(bi); assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true)); - assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(false)); - assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true)); - assertThat(hasIndexedWriteMethodForProperty(ebi, "foos"), is(false)); + assertEquals(hasIndexedWriteMethodForProperty(bi, "foos"), hasIndexedWriteMethodForProperty(ebi, "foos")); } /** @@ -595,7 +577,6 @@ public void cornerSpr10111() throws Exception { new ExtendedBeanInfo(Introspector.getBeanInfo(BigDecimal.class)); } - @Test public void subclassWriteMethodWithCovariantReturnType() throws IntrospectionException { @SuppressWarnings("unused") class B { @@ -792,6 +773,7 @@ public void propertyDescriptorOrderIsEqual() throws IntrospectionException { @Test public void propertyDescriptorComparator() throws IntrospectionException { PropertyDescriptorComparator c = new PropertyDescriptorComparator(); + assertThat(c.compare(new PropertyDescriptor("a", null, null), new PropertyDescriptor("a", null, null)), equalTo(0)); assertThat(c.compare(new PropertyDescriptor("abc", null, null), new PropertyDescriptor("abc", null, null)), equalTo(0)); assertThat(c.compare(new PropertyDescriptor("a", null, null), new PropertyDescriptor("b", null, null)), lessThan(0)); @@ -863,31 +845,6 @@ public void reproSpr8806() throws IntrospectionException { new ExtendedBeanInfo(Introspector.getBeanInfo(LawLibrary.class)); } - interface Book { } - - interface TextBook extends Book { } - - interface LawBook extends TextBook { } - - interface BookOperations { - Book getBook(); - void setBook(Book book); - } - - interface TextBookOperations extends BookOperations { - TextBook getBook(); - } - - abstract class Library { - public Book getBook() { return null; } - public void setBook(Book book) { } - } - - class LawLibrary extends Library implements TextBookOperations { - public LawBook getBook() { return null; } - } - - @Test public void cornerSpr8949() throws IntrospectionException { class A { @@ -905,23 +862,10 @@ public boolean isTargetMethod() { BeanInfo bi = Introspector.getBeanInfo(B.class); - /* first, demonstrate the 'problem': - * java.beans.Introspector returns the "wrong" declaring class for overridden read - * methods, which in turn violates expectations in {@link ExtendedBeanInfo} regarding - * method equality. Spring's {@link ClassUtils#getMostSpecificMethod(Method, Class)} - * helps out here, and is now put into use in ExtendedBeanInfo as well - */ - for (PropertyDescriptor pd : bi.getPropertyDescriptors()) { - if ("targetMethod".equals(pd.getName())) { - Method readMethod = pd.getReadMethod(); - assertTrue(readMethod.getDeclaringClass().equals(A.class)); // we expected B! - - Method msReadMethod = ClassUtils.getMostSpecificMethod(readMethod, B.class); - assertTrue(msReadMethod.getDeclaringClass().equals(B.class)); // and now we get it. - } - } - - // and now demonstrate that we've indeed fixed the problem + // java.beans.Introspector returns the "wrong" declaring class for overridden read + // methods, which in turn violates expectations in {@link ExtendedBeanInfo} regarding + // method equality. Spring's {@link ClassUtils#getMostSpecificMethod(Method, Class)} + // helps out here, and is now put into use in ExtendedBeanInfo as well. BeanInfo ebi = new ExtendedBeanInfo(bi); assertThat(hasReadMethodForProperty(bi, "targetMethod"), is(true)); @@ -973,8 +917,65 @@ public void shouldSupportStaticWriteMethod() throws IntrospectionException { } } + @Test // SPR-12434 + public void shouldDetectValidPropertiesAndIgnoreInvalidProperties() throws IntrospectionException { + BeanInfo bi = new ExtendedBeanInfo(Introspector.getBeanInfo(java.awt.Window.class)); + assertThat(hasReadMethodForProperty(bi, "locationByPlatform"), is(true)); + assertThat(hasWriteMethodForProperty(bi, "locationByPlatform"), is(true)); + assertThat(hasIndexedReadMethodForProperty(bi, "locationByPlatform"), is(false)); + assertThat(hasIndexedWriteMethodForProperty(bi, "locationByPlatform"), is(false)); + } + + + interface Spr9453 { + + T getProp(); + } + + interface Book { + } + + interface TextBook extends Book { + } + + interface LawBook extends TextBook { + } + + interface BookOperations { + + Book getBook(); + + void setBook(Book book); + } + + interface TextBookOperations extends BookOperations { + + @Override + TextBook getBook(); + } + + abstract class Library { + + public Book getBook() { + return null; + } + + public void setBook(Book book) { + } + } + + class LawLibrary extends Library implements TextBookOperations { + + @Override + public LawBook getBook() { + return null; + } + } + static class WithStaticWriteMethod { + public static void setProp1(String prop1) { } } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/SimplePropertyDescriptorTests.java b/spring-beans/src/test/java/org/springframework/beans/SimplePropertyDescriptorTests.java index 8627083279..b5a9f14e4f 100644 --- a/spring-beans/src/test/java/org/springframework/beans/SimplePropertyDescriptorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/SimplePropertyDescriptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,7 @@ import static org.junit.Assert.*; /** - * Unit tests for {@link SimpleNonIndexedPropertyDescriptor} and + * Unit tests for {@link SimplePropertyDescriptor} and * {@link SimpleIndexedPropertyDescriptor}. * * @author Chris Beams @@ -39,7 +39,7 @@ public class SimplePropertyDescriptorTests { @Test public void toStringOutput() throws IntrospectionException, SecurityException, NoSuchMethodException { { - Object pd = new SimpleNonIndexedPropertyDescriptor("foo", null, null); + Object pd = new SimplePropertyDescriptor("foo", null, null); assertThat(pd.toString(), containsString( "PropertyDescriptor[name=foo, propertyType=null, readMethod=null")); } @@ -49,7 +49,7 @@ class C { public Object setFoo(String foo) { return null; } } Method m = C.class.getMethod("setFoo", String.class); - Object pd = new SimpleNonIndexedPropertyDescriptor("foo", null, m); + Object pd = new SimplePropertyDescriptor("foo", null, m); assertThat(pd.toString(), allOf( containsString("PropertyDescriptor[name=foo"), containsString("propertyType=class java.lang.String"), @@ -76,10 +76,10 @@ class C { @Test public void nonIndexedEquality() throws IntrospectionException, SecurityException, NoSuchMethodException { - Object pd1 = new SimpleNonIndexedPropertyDescriptor("foo", null, null); + Object pd1 = new SimplePropertyDescriptor("foo", null, null); assertThat(pd1, equalTo(pd1)); - Object pd2 = new SimpleNonIndexedPropertyDescriptor("foo", null, null); + Object pd2 = new SimplePropertyDescriptor("foo", null, null); assertThat(pd1, equalTo(pd2)); assertThat(pd2, equalTo(pd1)); @@ -89,12 +89,12 @@ class C { public String getFoo() { return null; } } Method wm1 = C.class.getMethod("setFoo", String.class); - Object pd3 = new SimpleNonIndexedPropertyDescriptor("foo", null, wm1); + Object pd3 = new SimplePropertyDescriptor("foo", null, wm1); assertThat(pd1, not(equalTo(pd3))); assertThat(pd3, not(equalTo(pd1))); Method rm1 = C.class.getMethod("getFoo"); - Object pd4 = new SimpleNonIndexedPropertyDescriptor("foo", rm1, null); + Object pd4 = new SimplePropertyDescriptor("foo", rm1, null); assertThat(pd1, not(equalTo(pd4))); assertThat(pd4, not(equalTo(pd1))); @@ -147,4 +147,5 @@ class C { assertThat(pd1, not(equalTo(pd7))); assertThat(pd7, not(equalTo(pd1))); } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java index 553f9cef46..4e4c19b887 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2015 the original author 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,18 +16,13 @@ package org.springframework.beans.factory; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.springframework.tests.TestResourceUtils.qualifiedResource; - import java.util.Arrays; import java.util.List; import java.util.Map; import org.junit.Before; import org.junit.Test; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; + import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.StaticListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; @@ -39,6 +34,8 @@ import org.springframework.tests.sample.beans.factory.DummyFactory; import org.springframework.util.ObjectUtils; +import static org.junit.Assert.*; +import static org.springframework.tests.TestResourceUtils.*; /** * @author Rod Johnson @@ -54,16 +51,16 @@ public final class BeanFactoryUtilsTests { private static final Resource LEAF_CONTEXT = qualifiedResource(CLASS, "leaf.xml"); private static final Resource DEPENDENT_BEANS_CONTEXT = qualifiedResource(CLASS, "dependentBeans.xml"); - private ConfigurableListableBeanFactory listableBeanFactory; + private DefaultListableBeanFactory listableBeanFactory; + + private DefaultListableBeanFactory dependentBeansFactory; - private ConfigurableListableBeanFactory dependentBeansBF; @Before public void setUp() { // Interesting hierarchical factory to test counts. // Slow to read so we cache it. - DefaultListableBeanFactory grandParent = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(grandParent).loadBeanDefinitions(ROOT_CONTEXT); DefaultListableBeanFactory parent = new DefaultListableBeanFactory(grandParent); @@ -71,12 +68,13 @@ public void setUp() { DefaultListableBeanFactory child = new DefaultListableBeanFactory(parent); new XmlBeanDefinitionReader(child).loadBeanDefinitions(LEAF_CONTEXT); - this.dependentBeansBF = new DefaultListableBeanFactory(); - new XmlBeanDefinitionReader((BeanDefinitionRegistry) this.dependentBeansBF).loadBeanDefinitions(DEPENDENT_BEANS_CONTEXT); - dependentBeansBF.preInstantiateSingletons(); + this.dependentBeansFactory = new DefaultListableBeanFactory(); + new XmlBeanDefinitionReader(this.dependentBeansFactory).loadBeanDefinitions(DEPENDENT_BEANS_CONTEXT); + dependentBeansFactory.preInstantiateSingletons(); this.listableBeanFactory = child; } + @Test public void testHierarchicalCountBeansWithNonHierarchicalFactory() { StaticListableBeanFactory lbf = new StaticListableBeanFactory(); @@ -93,22 +91,21 @@ public void testHierarchicalCountBeansWithOverride() throws Exception { // Leaf count assertTrue(this.listableBeanFactory.getBeanDefinitionCount() == 1); // Count minus duplicate - assertTrue("Should count 7 beans, not " - + BeanFactoryUtils.countBeansIncludingAncestors(this.listableBeanFactory), - BeanFactoryUtils.countBeansIncludingAncestors(this.listableBeanFactory) == 7); + assertTrue("Should count 7 beans, not " + BeanFactoryUtils.countBeansIncludingAncestors(this.listableBeanFactory), + BeanFactoryUtils.countBeansIncludingAncestors(this.listableBeanFactory) == 7); } @Test public void testHierarchicalNamesWithNoMatch() throws Exception { - List names = Arrays.asList(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, - NoOp.class)); + List names = Arrays.asList( + BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, NoOp.class)); assertEquals(0, names.size()); } @Test public void testHierarchicalNamesWithMatchOnlyInRoot() throws Exception { - List names = Arrays.asList(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, - IndexedTestBean.class)); + List names = Arrays.asList( + BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, IndexedTestBean.class)); assertEquals(1, names.size()); assertTrue(names.contains("indexedBean")); // Distinguish from default ListableBeanFactory behavior @@ -117,8 +114,8 @@ public void testHierarchicalNamesWithMatchOnlyInRoot() throws Exception { @Test public void testGetBeanNamesForTypeWithOverride() throws Exception { - List names = Arrays.asList(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, - ITestBean.class)); + List names = Arrays.asList( + BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, ITestBean.class)); // includes 2 TestBeans from FactoryBeans (DummyFactory definitions) assertEquals(4, names.size()); assertTrue(names.contains("test")); @@ -131,7 +128,7 @@ public void testGetBeanNamesForTypeWithOverride() throws Exception { public void testNoBeansOfType() { StaticListableBeanFactory lbf = new StaticListableBeanFactory(); lbf.addBean("foo", new Object()); - Map beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf, ITestBean.class, true, false); + Map beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf, ITestBean.class, true, false); assertTrue(beans.isEmpty()); } @@ -148,7 +145,7 @@ public void testFindsBeansOfTypeWithStaticFactory() { lbf.addBean("t3", t3); lbf.addBean("t4", t4); - Map beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf, ITestBean.class, true, false); + Map beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf, ITestBean.class, true, false); assertEquals(2, beans.size()); assertEquals(t1, beans.get("t1")); assertEquals(t2, beans.get("t2")); @@ -192,8 +189,8 @@ public void testFindsBeansOfTypeWithDefaultFactory() { this.listableBeanFactory.registerSingleton("t3", t3); this.listableBeanFactory.registerSingleton("t4", t4); - Map beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.listableBeanFactory, ITestBean.class, true, - false); + Map beans = + BeanFactoryUtils.beansOfTypeIncludingAncestors(this.listableBeanFactory, ITestBean.class, true, false); assertEquals(6, beans.size()); assertEquals(test3, beans.get("test3")); assertEquals(test, beans.get("test")); @@ -201,12 +198,9 @@ public void testFindsBeansOfTypeWithDefaultFactory() { assertEquals(t2, beans.get("t2")); assertEquals(t3.getObject(), beans.get("t3")); assertTrue(beans.get("t4") instanceof TestBean); - // t3 and t4 are found here as of Spring 2.0, since they are - // pre-registered - // singleton instances, while testFactory1 and testFactory are *not* - // found - // because they are FactoryBean definitions that haven't been - // initialized yet. + // t3 and t4 are found here as of Spring 2.0, since they are pre-registered + // singleton instances, while testFactory1 and testFactory are *not* found + // because they are FactoryBean definitions that haven't been initialized yet. beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.listableBeanFactory, ITestBean.class, false, true); Object testFactory1 = this.listableBeanFactory.getBean("testFactory1"); @@ -248,8 +242,8 @@ public void testHierarchicalResolutionWithOverride() throws Exception { Object test3 = this.listableBeanFactory.getBean("test3"); Object test = this.listableBeanFactory.getBean("test"); - Map beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.listableBeanFactory, ITestBean.class, true, - false); + Map beans = + BeanFactoryUtils.beansOfTypeIncludingAncestors(this.listableBeanFactory, ITestBean.class, true, false); assertEquals(2, beans.size()); assertEquals(test3, beans.get("test3")); assertEquals(test, beans.get("test")); @@ -284,25 +278,25 @@ public void testHierarchicalResolutionWithOverride() throws Exception { @Test public void testADependencies() { - String[] deps = this.dependentBeansBF.getDependentBeans("a"); + String[] deps = this.dependentBeansFactory.getDependentBeans("a"); assertTrue(ObjectUtils.isEmpty(deps)); } @Test public void testBDependencies() { - String[] deps = this.dependentBeansBF.getDependentBeans("b"); + String[] deps = this.dependentBeansFactory.getDependentBeans("b"); assertTrue(Arrays.equals(new String[] { "c" }, deps)); } @Test public void testCDependencies() { - String[] deps = this.dependentBeansBF.getDependentBeans("c"); + String[] deps = this.dependentBeansFactory.getDependentBeans("c"); assertTrue(Arrays.equals(new String[] { "int", "long" }, deps)); } @Test public void testIntDependencies() { - String[] deps = this.dependentBeansBF.getDependentBeans("int"); + String[] deps = this.dependentBeansFactory.getDependentBeans("int"); assertTrue(Arrays.equals(new String[] { "buffer" }, deps)); } 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 73c2ab592a..a9a1dbe622 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-2013 the original author or authors. + * Copyright 2002-2015 the original author 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.Map; import java.util.Properties; import java.util.Set; - import javax.security.auth.Subject; import org.apache.commons.logging.Log; @@ -41,6 +40,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; + import org.springframework.beans.BeansException; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.NotWritablePropertyException; @@ -104,9 +104,11 @@ public class DefaultListableBeanFactoryTests { private static final Log factoryLog = LogFactory.getLog(DefaultListableBeanFactory.class); + @Rule public ExpectedException thrown = ExpectedException.none(); + @Test public void testUnreferencedSingletonWasInstantiated() { KnowsIfInstantiated.clearInstantiationRecord(); @@ -188,7 +190,7 @@ public void testPrototypeFactoryBeanIgnoredByNonEagerTypeMatching() { } @Test - public void testPrototypeSingletonFactoryBeanIgnoredByNonEagerTypeMatching() { + public void testSingletonFactoryBeanIgnoredByNonEagerTypeMatching() { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); Properties p = new Properties(); p.setProperty("x1.(class)", DummyFactory.class.getName()); @@ -712,6 +714,20 @@ public void testCanReferenceParentBeanFromChildViaAlias() { factory.getMergedBeanDefinition("child"), factory.getMergedBeanDefinition("child")); } + @Test + public void testGetTypeWorksAfterParentChildMerging() { + RootBeanDefinition parentDefinition = new RootBeanDefinition(TestBean.class); + ChildBeanDefinition childDefinition = new ChildBeanDefinition("parent", DerivedTestBean.class, null, null); + + DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); + factory.registerBeanDefinition("parent", parentDefinition); + factory.registerBeanDefinition("child", childDefinition); + factory.freezeConfiguration(); + + assertEquals(TestBean.class, factory.getType("parent")); + assertEquals(DerivedTestBean.class, factory.getType("child")); + } + @Test public void testNameAlreadyBound() { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); @@ -1196,8 +1212,8 @@ public void testAutowireWithTwoMatchesForConstructorDependency() { } catch (UnsatisfiedDependencyException ex) { // expected - assertTrue(ex.getMessage().indexOf("rod") != -1); - assertTrue(ex.getMessage().indexOf("rod2") != -1); + assertTrue(ex.getMessage().contains("rod")); + assertTrue(ex.getMessage().contains("rod2")); } } @@ -1267,13 +1283,13 @@ public void testAutowireBeanByNameWithNoDependencyCheck() { assertNull(bean.getSpouse()); } - @Test(expected=NoSuchBeanDefinitionException.class) + @Test(expected = NoSuchBeanDefinitionException.class) public void testGetBeanByTypeWithNoneFound() { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); lbf.getBean(TestBean.class); } - @Test(expected=NoUniqueBeanDefinitionException.class) + @Test(expected = NoUniqueBeanDefinitionException.class) public void testGetBeanByTypeWithAmbiguity() { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class); @@ -1377,7 +1393,7 @@ public void testGetTypeForAbstractFactoryBean() { * Java method names. In other words, you can't name a method * {@code set&FactoryBean(...)}. */ - @Test(expected=TypeMismatchException.class) + @Test(expected = TypeMismatchException.class) public void testAutowireBeanWithFactoryBeanByName() { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); RootBeanDefinition bd = new RootBeanDefinition(LazyInitFactory.class); @@ -1400,8 +1416,8 @@ public void testAutowireBeanByTypeWithTwoMatches() { } catch (UnsatisfiedDependencyException ex) { // expected - assertTrue(ex.getMessage().indexOf("test") != -1); - assertTrue(ex.getMessage().indexOf("spouse") != -1); + assertTrue(ex.getMessage().contains("test")); + assertTrue(ex.getMessage().contains("spouse")); } } @@ -1418,8 +1434,8 @@ public void testAutowireBeanByTypeWithTwoMatchesAndParameterNameDiscovery() { } catch (UnsatisfiedDependencyException ex) { // expected - assertTrue(ex.getMessage().indexOf("test") != -1); - assertTrue(ex.getMessage().indexOf("spouse") != -1); + assertTrue(ex.getMessage().contains("test")); + assertTrue(ex.getMessage().contains("spouse")); } } @@ -1667,7 +1683,7 @@ public void testBeanDefinitionWithInterface() { } catch (BeanCreationException ex) { assertEquals("test", ex.getBeanName()); - assertTrue(ex.getMessage().toLowerCase().indexOf("interface") != -1); + assertTrue(ex.getMessage().toLowerCase().contains("interface")); } } @@ -1681,7 +1697,7 @@ public void testBeanDefinitionWithAbstractClass() { } catch (BeanCreationException ex) { assertEquals("test", ex.getBeanName()); - assertTrue(ex.getMessage().toLowerCase().indexOf("abstract") != -1); + assertTrue(ex.getMessage().toLowerCase().contains("abstract")); } } @@ -2128,13 +2144,13 @@ private void findTypeOfPrototypeFactoryMethodOnBeanInstance(boolean singleton) { assertEquals(expectedNameFromArgs, tb2.getName()); } - @Test(expected=IllegalStateException.class) + @Test(expected = IllegalStateException.class) public void testScopingBeanToUnregisteredScopeResultsInAnException() throws Exception { BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(TestBean.class); AbstractBeanDefinition beanDefinition = builder.getBeanDefinition(); beanDefinition.setScope("he put himself so low could hardly look me in the face"); - final DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); + DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); factory.registerBeanDefinition("testBean", beanDefinition); factory.getBean("testBean"); } @@ -2146,8 +2162,7 @@ public void testExplicitScopeInheritanceForChildBeanDefinitions() throws Excepti RootBeanDefinition parent = new RootBeanDefinition(); parent.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); - AbstractBeanDefinition child = BeanDefinitionBuilder - .childBeanDefinition("parent").getBeanDefinition(); + AbstractBeanDefinition child = BeanDefinitionBuilder.childBeanDefinition("parent").getBeanDefinition(); child.setBeanClass(TestBean.class); child.setScope(theChildScope); @@ -2662,6 +2677,7 @@ public String getUserName() { } } + @SuppressWarnings("unused") private static class KnowsIfInstantiated { diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/FactoryBeanTests-abstract.xml b/spring-beans/src/test/java/org/springframework/beans/factory/FactoryBeanTests-abstract.xml new file mode 100644 index 0000000000..e82ccdc59b --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/FactoryBeanTests-abstract.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/FactoryBeanTests-circular.xml b/spring-beans/src/test/java/org/springframework/beans/factory/FactoryBeanTests-circular.xml new file mode 100644 index 0000000000..1c328aa8d4 --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/FactoryBeanTests-circular.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/FactoryBeanTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/FactoryBeanTests.java index 62c85b60b2..776cb3763e 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/FactoryBeanTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/FactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author 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,18 +16,24 @@ package org.springframework.beans.factory; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.springframework.tests.TestResourceUtils.qualifiedResource; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; + +import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.core.io.Resource; +import org.springframework.stereotype.Component; import org.springframework.util.Assert; +import static org.junit.Assert.*; +import static org.springframework.tests.TestResourceUtils.*; + /** * @author Rob Harrop * @author Juergen Hoeller @@ -38,6 +44,8 @@ public final class FactoryBeanTests { private static final Class CLASS = FactoryBeanTests.class; private static final Resource RETURNS_NULL_CONTEXT = qualifiedResource(CLASS, "returnsNull.xml"); private static final Resource WITH_AUTOWIRING_CONTEXT = qualifiedResource(CLASS, "withAutowiring.xml"); + private static final Resource ABSTRACT_CONTEXT = qualifiedResource(CLASS, "abstract.xml"); + private static final Resource CIRCULAR_CONTEXT = qualifiedResource(CLASS, "circular.xml"); @Test public void testFactoryBeanReturnsNull() throws Exception { @@ -80,6 +88,37 @@ public void testFactoryBeansWithIntermediateFactoryBeanAutowiringFailure() throw assertSame(gamma, beta.getGamma()); } + @Test + public void testAbstractFactoryBeanViaAnnotation() throws Exception { + DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); + new XmlBeanDefinitionReader(factory).loadBeanDefinitions(ABSTRACT_CONTEXT); + factory.getBeansWithAnnotation(Component.class); + } + + @Test + public void testAbstractFactoryBeanViaType() throws Exception { + DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); + new XmlBeanDefinitionReader(factory).loadBeanDefinitions(ABSTRACT_CONTEXT); + factory.getBeansOfType(AbstractFactoryBean.class); + } + + @Test + public void testCircularReferenceWithPostProcessor() { + DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); + new XmlBeanDefinitionReader(factory).loadBeanDefinitions(CIRCULAR_CONTEXT); + + CountingPostProcessor counter = new CountingPostProcessor(); + factory.addBeanPostProcessor(counter); + + BeanImpl1 impl1 = factory.getBean(BeanImpl1.class); + assertNotNull(impl1); + assertNotNull(impl1.getImpl2()); + assertNotNull(impl1.getImpl2()); + assertSame(impl1, impl1.getImpl2().getImpl1()); + assertEquals(1, counter.getCount("bean1")); + assertEquals(1, counter.getCount("bean2")); + } + public static class NullReturningFactoryBean implements FactoryBean { @@ -152,6 +191,7 @@ public static class Gamma { } + @Component public static class BetaFactoryBean implements FactoryBean { private Beta beta; @@ -176,4 +216,114 @@ public boolean isSingleton() { } } + + public abstract static class AbstractFactoryBean implements FactoryBean { + } + + + public static class PassThroughFactoryBean implements FactoryBean, BeanFactoryAware { + + private Class type; + + private String instanceName; + + private BeanFactory beanFactory; + + private T instance; + + public PassThroughFactoryBean(Class type) { + this.type = type; + } + + public void setInstanceName(String instanceName) { + this.instanceName = instanceName; + } + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + + @Override + public T getObject() { + if (instance == null) { + instance = beanFactory.getBean(instanceName, type); + } + return instance; + } + + @Override + public Class getObjectType() { + return type; + } + + @Override + public boolean isSingleton() { + return true; + } + } + + + public static class CountingPostProcessor implements BeanPostProcessor { + + private final Map count = new HashMap(); + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) { + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + if (bean instanceof FactoryBean) { + return bean; + } + AtomicInteger c = count.get(beanName); + if (c == null) { + c = new AtomicInteger(0); + count.put(beanName, c); + } + c.incrementAndGet(); + return bean; + } + + public int getCount(String beanName) { + AtomicInteger c = count.get(beanName); + if (c != null) { + return c.intValue(); + } + else { + return 0; + } + } + } + + + public static class BeanImpl1 { + + private BeanImpl2 impl2; + + public BeanImpl2 getImpl2() { + return impl2; + } + + public void setImpl2(BeanImpl2 impl2) { + this.impl2 = impl2; + } + } + + + public static class BeanImpl2 { + + private BeanImpl1 impl1; + + public BeanImpl1 getImpl1() { + return impl1; + } + + public void setImpl1(BeanImpl1 impl1) { + this.impl1 = impl1; + } + } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/annotation/AnnotationBeanWiringInfoResolverTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AnnotationBeanWiringInfoResolverTests.java similarity index 98% rename from spring-beans/src/test/java/org/springframework/beans/annotation/AnnotationBeanWiringInfoResolverTests.java rename to spring-beans/src/test/java/org/springframework/beans/factory/annotation/AnnotationBeanWiringInfoResolverTests.java index b623750f36..b4b9ec2a28 100644 --- a/spring-beans/src/test/java/org/springframework/beans/annotation/AnnotationBeanWiringInfoResolverTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AnnotationBeanWiringInfoResolverTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.beans.annotation; +package org.springframework.beans.factory.annotation; import static org.junit.Assert.*; 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 7181f0b8a1..dc2e7bdf33 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-2013 the original author or authors. + * Copyright 2002-2014 the original author 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,6 @@ package org.springframework.beans.factory.annotation; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - import java.io.Serializable; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -33,10 +25,12 @@ import java.util.Map; import org.junit.Test; + import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.AutowireCandidateQualifier; @@ -49,16 +43,15 @@ import org.springframework.tests.sample.beans.TestBean; import org.springframework.util.SerializationTestUtils; +import static org.junit.Assert.*; /** - * Unit tests for {@link AutowiredAnnotationBeanPostProcessor}. - * * @author Juergen Hoeller * @author Mark Fisher * @author Sam Brannen * @author Chris Beams */ -public final class AutowiredAnnotationBeanPostProcessorTests { +public class AutowiredAnnotationBeanPostProcessorTests { @Test public void testIncompleteBeanDefinition() { @@ -403,6 +396,23 @@ public void testConstructorResourceInjectionWithMultipleCandidates() { bf.destroySingletons(); } + @Test + public void testConstructorResourceInjectionWithNoCandidatesAndNoFallback() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ConstructorWithoutFallbackBean.class)); + + try { + bf.getBean("annotatedBean"); + fail("Should have thrown UnsatisfiedDependencyException"); + } + catch (UnsatisfiedDependencyException ex) { + // expected + } + } + @Test public void testConstructorResourceInjectionWithMultipleCandidatesAsCollection() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); @@ -963,7 +973,6 @@ public static class ResourceInjectionBean { private TestBean testBean2; - @Autowired public void setTestBean2(TestBean testBean2) { if (this.testBean2 != null) { @@ -1265,6 +1274,21 @@ public NestedTestBean[] getNestedTestBeans() { } + public static class ConstructorWithoutFallbackBean { + + protected ITestBean testBean3; + + @Autowired(required = false) + public ConstructorWithoutFallbackBean(ITestBean testBean3) { + this.testBean3 = testBean3; + } + + public ITestBean getTestBean3() { + return this.testBean3; + } + } + + public static class ConstructorsCollectionResourceInjectionBean { protected ITestBean testBean3; @@ -1330,7 +1354,6 @@ public static class MapFieldInjectionBean { @Autowired private Map testBeanMap; - public Map getTestBeanMap() { return this.testBeanMap; } diff --git a/spring-beans/src/test/java/org/springframework/beans/annotation/RequiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/RequiredAnnotationBeanPostProcessorTests.java similarity index 96% rename from spring-beans/src/test/java/org/springframework/beans/annotation/RequiredAnnotationBeanPostProcessorTests.java rename to spring-beans/src/test/java/org/springframework/beans/factory/annotation/RequiredAnnotationBeanPostProcessorTests.java index 7c728a55a2..04180cf06a 100644 --- a/spring-beans/src/test/java/org/springframework/beans/annotation/RequiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/RequiredAnnotationBeanPostProcessorTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.beans.annotation; +package org.springframework.beans.factory.annotation; import static org.junit.Assert.*; @@ -28,8 +28,6 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanNameAware; -import org.springframework.beans.factory.annotation.Required; -import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultListableBeanFactory; 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 fab64afdcb..6cccbc6d01 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-2013 the original author or authors. + * Copyright 2002-2015 the original author 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,15 +16,6 @@ package org.springframework.beans.factory.config; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition; -import static org.springframework.tests.TestResourceUtils.qualifiedResource; - import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -36,8 +27,8 @@ import java.util.prefs.Preferences; import java.util.prefs.PreferencesFactory; -import org.junit.Before; import org.junit.Test; + import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanInitializationException; @@ -52,19 +43,22 @@ import org.springframework.tests.sample.beans.IndexedTestBean; import org.springframework.tests.sample.beans.TestBean; +import static org.junit.Assert.*; +import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*; +import static org.springframework.tests.TestResourceUtils.*; /** * Unit tests for various {@link PropertyResourceConfigurer} implementations including: * {@link PropertyPlaceholderConfigurer}, {@link PropertyOverrideConfigurer} and * {@link PreferencesPlaceholderConfigurer}. * - * @see PropertyPlaceholderConfigurerTests - * @since 02.10.2003 * @author Juergen Hoeller * @author Chris Beams * @author Phillip Webb + * @since 02.10.2003 + * @see PropertyPlaceholderConfigurerTests */ -public final class PropertyResourceConfigurerTests { +public class PropertyResourceConfigurerTests { static { System.setProperty("java.util.prefs.PreferencesFactory", MockPreferencesFactory.class.getName()); @@ -75,23 +69,15 @@ public final class PropertyResourceConfigurerTests { private static final Resource XTEST_PROPS = qualifiedResource(CLASS, "xtest.properties"); // does not exist private static final Resource TEST_PROPS_XML = qualifiedResource(CLASS, "test.properties.xml"); - private DefaultListableBeanFactory factory; + private final DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); - @Before - public void setUp() { - factory = new DefaultListableBeanFactory(); - } @Test public void testPropertyOverrideConfigurer() { - BeanDefinition def1 = BeanDefinitionBuilder - .genericBeanDefinition(TestBean.class) - .getBeanDefinition(); + BeanDefinition def1 = BeanDefinitionBuilder.genericBeanDefinition(TestBean.class).getBeanDefinition(); factory.registerBeanDefinition("tb1", def1); - BeanDefinition def2 = BeanDefinitionBuilder - .genericBeanDefinition(TestBean.class) - .getBeanDefinition(); + BeanDefinition def2 = BeanDefinitionBuilder.genericBeanDefinition(TestBean.class).getBeanDefinition(); factory.registerBeanDefinition("tb2", def2); PropertyOverrideConfigurer poc1; @@ -128,9 +114,7 @@ public void testPropertyOverrideConfigurer() { @Test public void testPropertyOverrideConfigurerWithNestedProperty() { - BeanDefinition def = BeanDefinitionBuilder - .genericBeanDefinition(IndexedTestBean.class) - .getBeanDefinition(); + BeanDefinition def = BeanDefinitionBuilder.genericBeanDefinition(IndexedTestBean.class).getBeanDefinition(); factory.registerBeanDefinition("tb", def); PropertyOverrideConfigurer poc; @@ -148,9 +132,7 @@ public void testPropertyOverrideConfigurerWithNestedProperty() { @Test public void testPropertyOverrideConfigurerWithNestedPropertyAndDotInBeanName() { - BeanDefinition def = BeanDefinitionBuilder - .genericBeanDefinition(IndexedTestBean.class) - .getBeanDefinition(); + BeanDefinition def = BeanDefinitionBuilder.genericBeanDefinition(IndexedTestBean.class).getBeanDefinition(); factory.registerBeanDefinition("my.tb", def); PropertyOverrideConfigurer poc; @@ -169,9 +151,7 @@ public void testPropertyOverrideConfigurerWithNestedPropertyAndDotInBeanName() { @Test public void testPropertyOverrideConfigurerWithNestedMapPropertyAndDotInMapKey() { - BeanDefinition def = BeanDefinitionBuilder - .genericBeanDefinition(IndexedTestBean.class) - .getBeanDefinition(); + BeanDefinition def = BeanDefinitionBuilder.genericBeanDefinition(IndexedTestBean.class).getBeanDefinition(); factory.registerBeanDefinition("tb", def); PropertyOverrideConfigurer poc; @@ -189,9 +169,7 @@ public void testPropertyOverrideConfigurerWithNestedMapPropertyAndDotInMapKey() @Test public void testPropertyOverrideConfigurerWithHeldProperties() { - BeanDefinition def = BeanDefinitionBuilder - .genericBeanDefinition(PropertiesHolder.class) - .getBeanDefinition(); + BeanDefinition def = BeanDefinitionBuilder.genericBeanDefinition(PropertiesHolder.class).getBeanDefinition(); factory.registerBeanDefinition("tb", def); PropertyOverrideConfigurer poc; @@ -205,23 +183,9 @@ public void testPropertyOverrideConfigurerWithHeldProperties() { assertEquals("true", tb.getHeldProperties().getProperty("mail.smtp.auth")); } - static class PropertiesHolder { - private Properties props = new Properties(); - - public Properties getHeldProperties() { - return props; - } - - public void setHeldProperties(Properties props) { - this.props = props; - } - } - @Test public void testPropertyOverrideConfigurerWithPropertiesFile() { - BeanDefinition def = BeanDefinitionBuilder - .genericBeanDefinition(IndexedTestBean.class) - .getBeanDefinition(); + BeanDefinition def = BeanDefinitionBuilder.genericBeanDefinition(IndexedTestBean.class).getBeanDefinition(); factory.registerBeanDefinition("tb", def); PropertyOverrideConfigurer poc = new PropertyOverrideConfigurer(); @@ -235,13 +199,11 @@ public void testPropertyOverrideConfigurerWithPropertiesFile() { @Test public void testPropertyOverrideConfigurerWithInvalidPropertiesFile() { - BeanDefinition def = BeanDefinitionBuilder - .genericBeanDefinition(IndexedTestBean.class) - .getBeanDefinition(); + BeanDefinition def = BeanDefinitionBuilder.genericBeanDefinition(IndexedTestBean.class).getBeanDefinition(); factory.registerBeanDefinition("tb", def); PropertyOverrideConfigurer poc = new PropertyOverrideConfigurer(); - poc.setLocations(new Resource[] { TEST_PROPS, XTEST_PROPS }); + poc.setLocations(TEST_PROPS, XTEST_PROPS); poc.setIgnoreResourceNotFound(true); poc.postProcessBeanFactory(factory); @@ -252,9 +214,7 @@ public void testPropertyOverrideConfigurerWithInvalidPropertiesFile() { @Test public void testPropertyOverrideConfigurerWithPropertiesXmlFile() { - BeanDefinition def = BeanDefinitionBuilder - .genericBeanDefinition(IndexedTestBean.class) - .getBeanDefinition(); + BeanDefinition def = BeanDefinitionBuilder.genericBeanDefinition(IndexedTestBean.class).getBeanDefinition(); factory.registerBeanDefinition("tb", def); PropertyOverrideConfigurer poc = new PropertyOverrideConfigurer(); @@ -268,9 +228,7 @@ public void testPropertyOverrideConfigurerWithPropertiesXmlFile() { @Test public void testPropertyOverrideConfigurerWithConvertProperties() { - BeanDefinition def = BeanDefinitionBuilder - .genericBeanDefinition(IndexedTestBean.class) - .getBeanDefinition(); + BeanDefinition def = BeanDefinitionBuilder.genericBeanDefinition(IndexedTestBean.class).getBeanDefinition(); factory.registerBeanDefinition("tb", def); ConvertingOverrideConfigurer bfpp = new ConvertingOverrideConfigurer(); @@ -394,7 +352,6 @@ private void doTestPropertyPlaceholderConfigurer(boolean parentChildSeparation) cas.addGenericArgumentValue("${var}name${age}"); MutablePropertyValues pvs = new MutablePropertyValues(); - pvs.add("stringArray", new String[] {"${os.name}", "${age}"}); List friends = new ManagedList(); @@ -414,7 +371,7 @@ private void doTestPropertyPlaceholderConfigurer(boolean parentChildSeparation) someMap.put("key1", new RuntimeBeanReference("${ref}")); someMap.put("key2", "${age}name"); MutablePropertyValues innerPvs = new MutablePropertyValues(); - innerPvs.add("touchy", "${os.name}"); + innerPvs.add("country", "${os.name}"); RootBeanDefinition innerBd = new RootBeanDefinition(TestBean.class); innerBd.setPropertyValues(innerPvs); someMap.put("key3", innerBd); @@ -464,30 +421,28 @@ private void doTestPropertyPlaceholderConfigurer(boolean parentChildSeparation) TestBean inner2 = (TestBean) tb2.getSomeMap().get("mykey4"); assertEquals(0, inner1.getAge()); assertEquals(null, inner1.getName()); - assertEquals(System.getProperty("os.name"), inner1.getTouchy()); + assertEquals(System.getProperty("os.name"), inner1.getCountry()); assertEquals(98, inner2.getAge()); assertEquals("namemyvarmyvar${", inner2.getName()); - assertEquals(System.getProperty("os.name"), inner2.getTouchy()); + assertEquals(System.getProperty("os.name"), inner2.getCountry()); } @Test public void testPropertyPlaceholderConfigurerWithSystemPropertyFallback() { - factory.registerBeanDefinition("tb", - genericBeanDefinition(TestBean.class) - .addPropertyValue("touchy", "${os.name}").getBeanDefinition()); + factory.registerBeanDefinition("tb", genericBeanDefinition(TestBean.class) + .addPropertyValue("country", "${os.name}").getBeanDefinition()); PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); ppc.postProcessBeanFactory(factory); TestBean tb = (TestBean) factory.getBean("tb"); - assertEquals(System.getProperty("os.name"), tb.getTouchy()); + assertEquals(System.getProperty("os.name"), tb.getCountry()); } @Test public void testPropertyPlaceholderConfigurerWithSystemPropertyNotUsed() { - factory.registerBeanDefinition("tb", - genericBeanDefinition(TestBean.class) - .addPropertyValue("touchy", "${os.name}").getBeanDefinition()); + factory.registerBeanDefinition("tb", genericBeanDefinition(TestBean.class) + .addPropertyValue("country", "${os.name}").getBeanDefinition()); PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); Properties props = new Properties(); @@ -496,14 +451,13 @@ public void testPropertyPlaceholderConfigurerWithSystemPropertyNotUsed() { ppc.postProcessBeanFactory(factory); TestBean tb = (TestBean) factory.getBean("tb"); - assertEquals("myos", tb.getTouchy()); + assertEquals("myos", tb.getCountry()); } @Test public void testPropertyPlaceholderConfigurerWithOverridingSystemProperty() { - factory.registerBeanDefinition("tb", - genericBeanDefinition(TestBean.class) - .addPropertyValue("touchy", "${os.name}").getBeanDefinition()); + factory.registerBeanDefinition("tb", genericBeanDefinition(TestBean.class) + .addPropertyValue("country", "${os.name}").getBeanDefinition()); PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); Properties props = new Properties(); @@ -513,14 +467,13 @@ public void testPropertyPlaceholderConfigurerWithOverridingSystemProperty() { ppc.postProcessBeanFactory(factory); TestBean tb = (TestBean) factory.getBean("tb"); - assertEquals(System.getProperty("os.name"), tb.getTouchy()); + assertEquals(System.getProperty("os.name"), tb.getCountry()); } @Test public void testPropertyPlaceholderConfigurerWithUnresolvableSystemProperty() { - factory.registerBeanDefinition("tb", - genericBeanDefinition(TestBean.class) - .addPropertyValue("touchy", "${user.dir}").getBeanDefinition()); + factory.registerBeanDefinition("tb", genericBeanDefinition(TestBean.class) + .addPropertyValue("touchy", "${user.dir}").getBeanDefinition()); PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); ppc.setSystemPropertiesMode(PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_NEVER); @@ -537,9 +490,8 @@ public void testPropertyPlaceholderConfigurerWithUnresolvableSystemProperty() { @Test public void testPropertyPlaceholderConfigurerWithUnresolvablePlaceholder() { - factory.registerBeanDefinition("tb", - genericBeanDefinition(TestBean.class) - .addPropertyValue("name", "${ref}").getBeanDefinition()); + factory.registerBeanDefinition("tb", genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "${ref}").getBeanDefinition()); PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); @@ -555,9 +507,8 @@ public void testPropertyPlaceholderConfigurerWithUnresolvablePlaceholder() { @Test public void testPropertyPlaceholderConfigurerWithIgnoreUnresolvablePlaceholder() { - factory.registerBeanDefinition("tb", - genericBeanDefinition(TestBean.class) - .addPropertyValue("name", "${ref}").getBeanDefinition()); + factory.registerBeanDefinition("tb", genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "${ref}").getBeanDefinition()); PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); ppc.setIgnoreUnresolvablePlaceholders(true); @@ -569,9 +520,8 @@ public void testPropertyPlaceholderConfigurerWithIgnoreUnresolvablePlaceholder() @Test public void testPropertyPlaceholderConfigurerWithEmptyStringAsNull() { - factory.registerBeanDefinition("tb", - genericBeanDefinition(TestBean.class) - .addPropertyValue("name", "").getBeanDefinition()); + factory.registerBeanDefinition("tb", genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "").getBeanDefinition()); PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); ppc.setNullValue(""); @@ -583,9 +533,8 @@ public void testPropertyPlaceholderConfigurerWithEmptyStringAsNull() { @Test public void testPropertyPlaceholderConfigurerWithEmptyStringInPlaceholderAsNull() { - factory.registerBeanDefinition("tb", - genericBeanDefinition(TestBean.class) - .addPropertyValue("name", "${ref}").getBeanDefinition()); + factory.registerBeanDefinition("tb", genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "${ref}").getBeanDefinition()); PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); ppc.setNullValue(""); @@ -600,9 +549,8 @@ public void testPropertyPlaceholderConfigurerWithEmptyStringInPlaceholderAsNull( @Test public void testPropertyPlaceholderConfigurerWithNestedPlaceholderInKey() { - factory.registerBeanDefinition("tb", - genericBeanDefinition(TestBean.class) - .addPropertyValue("name", "${my${key}key}").getBeanDefinition()); + factory.registerBeanDefinition("tb", genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "${my${key}key}").getBeanDefinition()); PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); Properties props = new Properties(); @@ -617,8 +565,7 @@ public void testPropertyPlaceholderConfigurerWithNestedPlaceholderInKey() { @Test public void testPropertyPlaceholderConfigurerWithPlaceholderInAlias() { - factory.registerBeanDefinition("tb", - genericBeanDefinition(TestBean.class).getBeanDefinition()); + factory.registerBeanDefinition("tb", genericBeanDefinition(TestBean.class).getBeanDefinition()); factory.registerAlias("tb", "${alias}"); PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); @@ -634,8 +581,7 @@ public void testPropertyPlaceholderConfigurerWithPlaceholderInAlias() { @Test public void testPropertyPlaceholderConfigurerWithSelfReferencingPlaceholderInAlias() { - factory.registerBeanDefinition("tb", - genericBeanDefinition(TestBean.class).getBeanDefinition()); + factory.registerBeanDefinition("tb", genericBeanDefinition(TestBean.class).getBeanDefinition()); factory.registerAlias("tb", "${alias}"); PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); @@ -651,11 +597,10 @@ public void testPropertyPlaceholderConfigurerWithSelfReferencingPlaceholderInAli @Test public void testPropertyPlaceholderConfigurerWithCircularReference() { - factory.registerBeanDefinition("tb", - genericBeanDefinition(TestBean.class) - .addPropertyValue("age", "${age}") - .addPropertyValue("name", "name${var}") - .getBeanDefinition()); + factory.registerBeanDefinition("tb", genericBeanDefinition(TestBean.class) + .addPropertyValue("age", "${age}") + .addPropertyValue("name", "name${var}") + .getBeanDefinition()); PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); Properties props = new Properties(); @@ -675,9 +620,8 @@ public void testPropertyPlaceholderConfigurerWithCircularReference() { @Test public void testPropertyPlaceholderConfigurerWithDefaultProperties() { - factory.registerBeanDefinition("tb", - genericBeanDefinition(TestBean.class) - .addPropertyValue("touchy", "${test}").getBeanDefinition()); + factory.registerBeanDefinition("tb", genericBeanDefinition(TestBean.class) + .addPropertyValue("touchy", "${test}").getBeanDefinition()); PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); Properties props = new Properties(); @@ -691,9 +635,8 @@ public void testPropertyPlaceholderConfigurerWithDefaultProperties() { @Test public void testPropertyPlaceholderConfigurerWithInlineDefault() { - factory.registerBeanDefinition("tb", - genericBeanDefinition(TestBean.class) - .addPropertyValue("touchy", "${test:mytest}").getBeanDefinition()); + factory.registerBeanDefinition("tb", genericBeanDefinition(TestBean.class) + .addPropertyValue("touchy", "${test:mytest}").getBeanDefinition()); PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); ppc.postProcessBeanFactory(factory); @@ -704,9 +647,8 @@ public void testPropertyPlaceholderConfigurerWithInlineDefault() { @Test public void testPropertyPlaceholderConfigurerWithAliases() { - factory.registerBeanDefinition("tb", - genericBeanDefinition(TestBean.class) - .addPropertyValue("touchy", "${test}").getBeanDefinition()); + factory.registerBeanDefinition("tb", genericBeanDefinition(TestBean.class) + .addPropertyValue("touchy", "${test}").getBeanDefinition()); factory.registerAlias("tb", "${myAlias}"); factory.registerAlias("${myTarget}", "alias2"); @@ -729,12 +671,11 @@ public void testPropertyPlaceholderConfigurerWithAliases() { @Test public void testPreferencesPlaceholderConfigurer() { - factory.registerBeanDefinition("tb", - genericBeanDefinition(TestBean.class) - .addPropertyValue("name", "${myName}") - .addPropertyValue("age", "${myAge}") - .addPropertyValue("touchy", "${myTouchy}") - .getBeanDefinition()); + factory.registerBeanDefinition("tb", genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "${myName}") + .addPropertyValue("age", "${myAge}") + .addPropertyValue("touchy", "${myTouchy}") + .getBeanDefinition()); PreferencesPlaceholderConfigurer ppc = new PreferencesPlaceholderConfigurer(); Properties props = new Properties(); @@ -757,12 +698,11 @@ public void testPreferencesPlaceholderConfigurer() { @Test public void testPreferencesPlaceholderConfigurerWithCustomTreePaths() { - factory.registerBeanDefinition("tb", - genericBeanDefinition(TestBean.class) - .addPropertyValue("name", "${myName}") - .addPropertyValue("age", "${myAge}") - .addPropertyValue("touchy", "${myTouchy}") - .getBeanDefinition()); + factory.registerBeanDefinition("tb", genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "${myName}") + .addPropertyValue("age", "${myAge}") + .addPropertyValue("touchy", "${myTouchy}") + .getBeanDefinition()); PreferencesPlaceholderConfigurer ppc = new PreferencesPlaceholderConfigurer(); Properties props = new Properties(); @@ -787,12 +727,11 @@ public void testPreferencesPlaceholderConfigurerWithCustomTreePaths() { @Test public void testPreferencesPlaceholderConfigurerWithPathInPlaceholder() { - factory.registerBeanDefinition("tb", - genericBeanDefinition(TestBean.class) - .addPropertyValue("name", "${mypath/myName}") - .addPropertyValue("age", "${myAge}") - .addPropertyValue("touchy", "${myotherpath/myTouchy}") - .getBeanDefinition()); + factory.registerBeanDefinition("tb", genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "${mypath/myName}") + .addPropertyValue("age", "${myAge}") + .addPropertyValue("touchy", "${myotherpath/myTouchy}") + .getBeanDefinition()); PreferencesPlaceholderConfigurer ppc = new PreferencesPlaceholderConfigurer(); Properties props = new Properties(); @@ -816,6 +755,20 @@ public void testPreferencesPlaceholderConfigurerWithPathInPlaceholder() { } + static class PropertiesHolder { + + private Properties props = new Properties(); + + public Properties getHeldProperties() { + return props; + } + + public void setHeldProperties(Properties props) { + this.props = props; + } + } + + private static class ConvertingOverrideConfigurer extends PropertyOverrideConfigurer { @Override @@ -824,26 +777,28 @@ protected String convertPropertyValue(String originalValue) { } } + /** * {@link PreferencesFactory} to create {@link MockPreferences}. */ public static class MockPreferencesFactory implements PreferencesFactory { - private Preferences systemRoot = new MockPreferences(); + private final Preferences userRoot = new MockPreferences(); - private Preferences userRoot = new MockPreferences(); + private final Preferences systemRoot = new MockPreferences(); @Override public Preferences systemRoot() { - return systemRoot; + return this.systemRoot; } @Override public Preferences userRoot() { - return userRoot; + return this.userRoot; } } + /** * Mock implementation of {@link Preferences} that behaves the same regardless of the * underlying operating system and will never throw security exceptions. @@ -909,4 +864,5 @@ protected void syncSpi() throws BackingStoreException { protected void flushSpi() throws BackingStoreException { } } + } 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 new file mode 100644 index 0000000000..56fcde6ec9 --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/AutowireUtilsTests.java @@ -0,0 +1,192 @@ +/* + * Copyright 2002-2013 the original author 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.support; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; + +import org.springframework.util.ReflectionUtils; + +import static org.junit.Assert.*; + +/** + * @author Juergen Hoeller + * @author Sam Brannen + */ +public class AutowireUtilsTests { + + @Test + public void genericMethodReturnTypes() { + Method notParameterized = ReflectionUtils.findMethod(MyTypeWithMethods.class, "notParameterized", new Class[]{}); + assertEquals(String.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(notParameterized, new Object[]{}, getClass().getClassLoader())); + + Method notParameterizedWithArguments = ReflectionUtils.findMethod(MyTypeWithMethods.class, "notParameterizedWithArguments", + new Class[] { Integer.class, Boolean.class }); + assertEquals(String.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(notParameterizedWithArguments, new Object[] { 99, true }, getClass().getClassLoader())); + + Method createProxy = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createProxy", new Class[] { Object.class }); + assertEquals(String.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(createProxy, new Object[] { "foo" }, getClass().getClassLoader())); + + Method createNamedProxyWithDifferentTypes = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedProxy", + new Class[] { String.class, Object.class }); + assertEquals(Long.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDifferentTypes, new Object[] { "enigma", 99L }, getClass().getClassLoader())); + + Method createNamedProxyWithDuplicateTypes = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedProxy", + new Class[] { String.class, Object.class }); + assertEquals(String.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDuplicateTypes, new Object[] { "enigma", "foo" }, getClass().getClassLoader())); + + Method createMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createMock", new Class[] { Class.class }); + assertEquals(Runnable.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(createMock, new Object[] { Runnable.class }, getClass().getClassLoader())); + assertEquals(Runnable.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(createMock, new Object[] { Runnable.class.getName() }, getClass().getClassLoader())); + + Method createNamedMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedMock", new Class[] { String.class, + Class.class }); + assertEquals(Runnable.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedMock, new Object[] { "foo", Runnable.class }, getClass().getClassLoader())); + + Method createVMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createVMock", + new Class[] { Object.class, Class.class }); + assertEquals(Runnable.class, + 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 }); + assertEquals(Object.class, + 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.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())); + } + + + public interface MyInterfaceType { + } + + public class MySimpleInterfaceType implements MyInterfaceType { + } + + public static class MyTypeWithMethods { + + public MyInterfaceType integer() { + return null; + } + + public MySimpleInterfaceType string() { + return null; + } + + public Object object() { + return null; + } + + @SuppressWarnings("rawtypes") + public MyInterfaceType raw() { + return null; + } + + public String notParameterized() { + return null; + } + + public String notParameterizedWithArguments(Integer x, Boolean b) { + return null; + } + + /** + * Simulates a factory method that wraps the supplied object in a proxy of the + * same type. + */ + public static T createProxy(T object) { + return null; + } + + /** + * Similar to {@link #createProxy(Object)} but adds an additional argument before + * the argument of type {@code T}. Note that they may potentially be of the same + * time when invoked! + */ + public static T createNamedProxy(String name, T object) { + return null; + } + + /** + * Simulates factory methods found in libraries such as Mockito and EasyMock. + */ + public static MOCK createMock(Class toMock) { + return null; + } + + /** + * Similar to {@link #createMock(Class)} but adds an additional method argument + * before the parameterized argument. + */ + public static T createNamedMock(String name, Class toMock) { + return null; + } + + /** + * Similar to {@link #createNamedMock(String, Class)} but adds an additional + * parameterized type. + */ + public static T createVMock(V name, Class toMock) { + return null; + } + + /** + * Extract some value of the type supported by the interface (i.e., by a concrete, + * non-generic implementation of the interface). + */ + public static T extractValueFrom(MyInterfaceType myInterfaceType) { + return null; + } + + /** + * Extract some magic value from the supplied map. + */ + public static V extractMagicValue(Map map) { + return null; + } + + public void readIntegerInputMessage(MyInterfaceType message) { + } + + public void readIntegerArrayInputMessage(MyInterfaceType[] message) { + } + + public void readGenericArrayInputMessage(T[] message) { + } + } + +} 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 2480f64fac..f2b33f22dc 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 @@ -16,13 +16,9 @@ package org.springframework.beans.factory.support; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; @@ -37,21 +33,23 @@ import org.junit.Test; import org.mockito.Mockito; + import org.springframework.beans.PropertyEditorRegistrar; import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.config.TypedStringValue; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.beans.propertyeditors.CustomNumberEditor; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.UrlResource; import org.springframework.tests.Assume; import org.springframework.tests.TestGroup; - import org.springframework.tests.sample.beans.GenericBean; import org.springframework.tests.sample.beans.GenericIntegerBean; import org.springframework.tests.sample.beans.GenericSetOfIntegerBean; import org.springframework.tests.sample.beans.TestBean; +import static org.junit.Assert.*; /** * @author Juergen Hoeller @@ -115,7 +113,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); @@ -655,18 +653,17 @@ public void testSetBean() throws Exception { } /** - * Tests support for parameterized {@code factory-method} declarations such - * as Mockito {@code mock()} method which has the following signature. - * - *
{@code
+	 * Tests support for parameterized static {@code factory-method} declarations such as
+	 * Mockito's {@code mock()} method which has the following signature.
+	 * 
+	 * {@code
 	 * public static  T mock(Class classToMock)
-	 * }
- * - * See SPR-9493 - * @since 3.2 + * } + *
+ *

See SPR-9493 */ @Test - public void parameterizedFactoryMethod() { + public void parameterizedStaticFactoryMethod() { RootBeanDefinition rbd = new RootBeanDefinition(Mockito.class); rbd.setFactoryMethodName("mock"); rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class); @@ -678,6 +675,100 @@ public void parameterizedFactoryMethod() { assertEquals(1, beans.size()); } + /** + * Tests support for parameterized instance {@code factory-method} declarations such + * as EasyMock's {@code IMocksControl.createMock()} method which has the following + * signature. + *

+	 * {@code
+	 * public  T createMock(Class toMock)
+	 * }
+	 * 
+ *

See SPR-10411 + */ + @Test + public void parameterizedInstanceFactoryMethod() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + + 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); + + Map beans = bf.getBeansOfType(Runnable.class); + assertEquals(1, beans.size()); + } + + @Test + public void parameterizedInstanceFactoryMethodWithNonResolvedClassName() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + + RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); + bf.registerBeanDefinition("mocksControl", rbd); + + rbd = new RootBeanDefinition(); + rbd.setFactoryBeanName("mocksControl"); + rbd.setFactoryMethodName("createMock"); + rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class.getName()); + bf.registerBeanDefinition("mock", rbd); + + Map beans = bf.getBeansOfType(Runnable.class); + assertEquals(1, beans.size()); + } + + @Test + public void parameterizedInstanceFactoryMethodWithWrappedClassName() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + + RootBeanDefinition rbd = new RootBeanDefinition(); + rbd.setBeanClassName(Mockito.class.getName()); + rbd.setFactoryMethodName("mock"); + // TypedStringValue used to be equivalent to an XML-defined argument String + rbd.getConstructorArgumentValues().addGenericArgumentValue(new TypedStringValue(Runnable.class.getName())); + bf.registerBeanDefinition("mock", rbd); + + Map beans = bf.getBeansOfType(Runnable.class); + assertEquals(1, beans.size()); + } + + @Test + public void parameterizedInstanceFactoryMethodWithInvalidClassName() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + + RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); + bf.registerBeanDefinition("mocksControl", rbd); + + rbd = new RootBeanDefinition(); + rbd.setFactoryBeanName("mocksControl"); + rbd.setFactoryMethodName("createMock"); + rbd.getConstructorArgumentValues().addGenericArgumentValue("x"); + bf.registerBeanDefinition("mock", rbd); + + Map beans = bf.getBeansOfType(Runnable.class); + assertEquals(0, beans.size()); + } + + @Test + public void parameterizedInstanceFactoryMethodWithIndexedArgument() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + + RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); + bf.registerBeanDefinition("mocksControl", rbd); + + rbd = new RootBeanDefinition(); + rbd.setFactoryBeanName("mocksControl"); + rbd.setFactoryMethodName("createMock"); + rbd.getConstructorArgumentValues().addIndexedArgumentValue(0, Runnable.class); + bf.registerBeanDefinition("mock", rbd); + + Map beans = bf.getBeansOfType(Runnable.class); + assertEquals(1, beans.size()); + } + @SuppressWarnings("serial") public static class NamedUrlList extends LinkedList { @@ -722,4 +813,22 @@ public void setUrlNames(Set urlNames) throws MalformedURLException { } } + + /** + * Pseudo-implementation of EasyMock's {@code MocksControl} class. + */ + public static class MocksControl { + + @SuppressWarnings("unchecked") + public T createMock(Class toMock) { + return (T) Proxy.newProxyInstance(BeanFactoryGenericsTests.class.getClassLoader(), new Class[] {toMock}, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + throw new UnsupportedOperationException("mocked!"); + } + }); + } + } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/DefinitionMetadataEqualsHashCodeTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/DefinitionMetadataEqualsHashCodeTests.java index f9f1beba04..7e44b200b3 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/DefinitionMetadataEqualsHashCodeTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/DefinitionMetadataEqualsHashCodeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author 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,63 +16,95 @@ package org.springframework.beans.factory.support; -import junit.framework.TestCase; +import org.junit.Test; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.tests.sample.beans.TestBean; +import static org.junit.Assert.*; /** + * Unit tests for {@code equals()} and {@code hashCode()} in bean definitions. + * * @author Rob Harrop + * @author Sam Brannen */ -public class DefinitionMetadataEqualsHashCodeTests extends TestCase { +@SuppressWarnings("serial") +public class DefinitionMetadataEqualsHashCodeTests { - @SuppressWarnings("serial") - public void testRootBeanDefinitionEqualsAndHashCode() throws Exception { + @Test + public void rootBeanDefinition() { RootBeanDefinition master = new RootBeanDefinition(TestBean.class); RootBeanDefinition equal = new RootBeanDefinition(TestBean.class); RootBeanDefinition notEqual = new RootBeanDefinition(String.class); - RootBeanDefinition subclass = new RootBeanDefinition(TestBean.class) {}; + RootBeanDefinition subclass = new RootBeanDefinition(TestBean.class) { + }; setBaseProperties(master); setBaseProperties(equal); setBaseProperties(notEqual); setBaseProperties(subclass); - assertEqualsContract(master, equal, notEqual, subclass); - assertEquals("Hash code for equal instances should match", master.hashCode(), equal.hashCode()); + assertEqualsAndHashCodeContracts(master, equal, notEqual, subclass); } - @SuppressWarnings("serial") - public void testChildBeanDefinitionEqualsAndHashCode() throws Exception { + /** + * @since 3.2.8 + * @see SPR-11420 + */ + @Test + public void rootBeanDefinitionAndMethodOverridesWithDifferentOverloadedValues() { + RootBeanDefinition master = new RootBeanDefinition(TestBean.class); + RootBeanDefinition equal = new RootBeanDefinition(TestBean.class); + + setBaseProperties(master); + setBaseProperties(equal); + + // Simulate AbstractBeanDefinition.validate() which delegates to + // AbstractBeanDefinition.prepareMethodOverrides(): + master.getMethodOverrides().getOverrides().iterator().next().setOverloaded(false); + // But do not simulate validation of the 'equal' bean. As a consequence, a method + // override in 'equal' will be marked as overloaded, but the corresponding + // override in 'master' will not. But... the bean definitions should still be + // considered equal. + + assertEquals("Should be equal", master, equal); + assertEquals("Hash code for equal instances must match", master.hashCode(), equal.hashCode()); + } + + @Test + public void childBeanDefinition() { ChildBeanDefinition master = new ChildBeanDefinition("foo"); ChildBeanDefinition equal = new ChildBeanDefinition("foo"); ChildBeanDefinition notEqual = new ChildBeanDefinition("bar"); - ChildBeanDefinition subclass = new ChildBeanDefinition("foo"){}; + ChildBeanDefinition subclass = new ChildBeanDefinition("foo") { + }; setBaseProperties(master); setBaseProperties(equal); setBaseProperties(notEqual); setBaseProperties(subclass); - assertEqualsContract(master, equal, notEqual, subclass); - assertEquals("Hash code for equal instances should match", master.hashCode(), equal.hashCode()); + assertEqualsAndHashCodeContracts(master, equal, notEqual, subclass); } - public void testRuntimeBeanReference() throws Exception { + @Test + public void runtimeBeanReference() { RuntimeBeanReference master = new RuntimeBeanReference("name"); RuntimeBeanReference equal = new RuntimeBeanReference("name"); RuntimeBeanReference notEqual = new RuntimeBeanReference("someOtherName"); - RuntimeBeanReference subclass = new RuntimeBeanReference("name"){}; - assertEqualsContract(master, equal, notEqual, subclass); + RuntimeBeanReference subclass = new RuntimeBeanReference("name") { + }; + assertEqualsAndHashCodeContracts(master, equal, notEqual, subclass); } + private void setBaseProperties(AbstractBeanDefinition definition) { definition.setAbstract(true); definition.setAttribute("foo", "bar"); definition.setAutowireCandidate(false); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); - //definition.getConstructorArgumentValues().addGenericArgumentValue("foo"); + // definition.getConstructorArgumentValues().addGenericArgumentValue("foo"); definition.setDependencyCheck(AbstractBeanDefinition.DEPENDENCY_CHECK_OBJECTS); - definition.setDependsOn(new String[]{"foo", "bar"}); + definition.setDependsOn(new String[] { "foo", "bar" }); definition.setDestroyMethodName("destroy"); definition.setEnforceDestroyMethod(false); definition.setEnforceInitMethod(true); @@ -89,10 +121,15 @@ private void setBaseProperties(AbstractBeanDefinition definition) { definition.setSource("foo"); } - private void assertEqualsContract(Object master, Object equal, Object notEqual, Object subclass) { + private void assertEqualsAndHashCodeContracts(Object master, Object equal, Object notEqual, Object subclass) { assertEquals("Should be equal", master, equal); - assertFalse("Should not be equal", master.equals(notEqual)); + assertEquals("Hash code for equal instances should match", master.hashCode(), equal.hashCode()); + + assertNotEquals("Should not be equal", master, notEqual); + assertNotEquals("Hash code for non-equal instances should not match", master.hashCode(), notEqual.hashCode()); + assertEquals("Subclass should be equal", master, subclass); + assertEquals("Hash code for subclass should match", master.hashCode(), subclass.hashCode()); } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/Spr8954Tests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/Spr8954Tests.java index 411f538045..719381c6b4 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/Spr8954Tests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/Spr8954Tests.java @@ -16,59 +16,59 @@ package org.springframework.beans.factory.support; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; - +import java.util.Arrays; import java.util.Map; +import org.junit.Before; import org.junit.Test; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.beans.factory.support.RootBeanDefinition; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; /** * Unit tests for SPR-8954, in which a custom {@link InstantiationAwareBeanPostProcessor} * forces the predicted type of a FactoryBean, effectively preventing retrieval of the * bean from calls to #getBeansOfType(FactoryBean.class). The implementation of - * {@link AbstractBeanFactory#isFactoryBean(String, RootBeanDefinition)} now ensures - * that not only the predicted bean type is considered, but also the original bean - * definition's beanClass. + * {@link AbstractBeanFactory#isFactoryBean(String, RootBeanDefinition)} now ensures that + * not only the predicted bean type is considered, but also the original bean definition's + * beanClass. * * @author Chris Beams * @author Oliver Gierke */ public class Spr8954Tests { - @Test - public void repro() { - DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + private DefaultListableBeanFactory bf; + + @Before + public void setUp() { + bf = new DefaultListableBeanFactory(); bf.registerBeanDefinition("foo", new RootBeanDefinition(FooFactoryBean.class)); bf.addBeanPostProcessor(new PredictingBPP()); + } + @Test + public void repro() { assertThat(bf.getBean("foo"), instanceOf(Foo.class)); assertThat(bf.getBean("&foo"), instanceOf(FooFactoryBean.class)); - assertThat(bf.isTypeMatch("&foo", FactoryBean.class), is(true)); @SuppressWarnings("rawtypes") Map fbBeans = bf.getBeansOfType(FactoryBean.class); - assertThat(1, equalTo(fbBeans.size())); - assertThat("&foo", equalTo(fbBeans.keySet().iterator().next())); + assertThat(fbBeans.size(), is(1)); + assertThat(fbBeans.keySet(), hasItem("&foo")); Map aiBeans = bf.getBeansOfType(AnInterface.class); - assertThat(1, equalTo(aiBeans.size())); - assertThat("&foo", equalTo(aiBeans.keySet().iterator().next())); + assertThat(aiBeans.size(), is(1)); + assertThat(aiBeans.keySet(), hasItem("&foo")); } @Test public void findsBeansByTypeIfNotInstantiated() { - DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - bf.registerBeanDefinition("foo", new RootBeanDefinition(FooFactoryBean.class)); - bf.addBeanPostProcessor(new PredictingBPP()); - assertThat(bf.isTypeMatch("&foo", FactoryBean.class), is(true)); @SuppressWarnings("rawtypes") @@ -77,8 +77,21 @@ public void findsBeansByTypeIfNotInstantiated() { assertThat("&foo", equalTo(fbBeans.keySet().iterator().next())); Map aiBeans = bf.getBeansOfType(AnInterface.class); - assertThat(1, equalTo(aiBeans.size())); - assertThat("&foo", equalTo(aiBeans.keySet().iterator().next())); + assertThat(aiBeans.size(), is(1)); + assertThat(aiBeans.keySet(), hasItem("&foo")); + } + + /** + * SPR-10517 + */ + @Test + public void findsFactoryBeanNameByTypeWithoutInstantiation() { + String[] names = bf.getBeanNamesForType(AnInterface.class, false, false); + assertThat(Arrays.asList(names), hasItem("&foo")); + + Map beans = bf.getBeansOfType(AnInterface.class, false, false); + assertThat(beans.size(), is(1)); + assertThat(beans.keySet(), hasItem("&foo")); } @@ -116,8 +129,7 @@ static class PredictingBPP extends InstantiationAwareBeanPostProcessorAdapter { @Override public Class predictBeanType(Class beanClass, String beanName) { - return FactoryBean.class.isAssignableFrom(beanClass) ? - PredictedType.class : null; + return FactoryBean.class.isAssignableFrom(beanClass) ? PredictedType.class : null; } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/xml/FactoryMethodTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/xml/FactoryMethodTests.java index 0d67448bc0..c1ab301059 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/xml/FactoryMethodTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/xml/FactoryMethodTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author 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,6 @@ import java.util.List; import java.util.Properties; -import static org.junit.Assert.*; import org.junit.Test; import org.springframework.beans.factory.BeanCreationException; @@ -30,6 +29,8 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.tests.sample.beans.TestBean; +import static org.junit.Assert.*; + /** * @author Juergen Hoeller * @author Chris Beams @@ -259,6 +260,34 @@ public void testFactoryMethodNoMatchingStaticMethod() { } } + @Test + public void testNonExistingFactoryMethod() { + DefaultListableBeanFactory xbf = new DefaultListableBeanFactory(); + XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(xbf); + reader.loadBeanDefinitions(new ClassPathResource("factory-methods.xml", getClass())); + try { + xbf.getBean("invalidPrototype"); + fail("Should have thrown BeanCreationException"); + } + catch (BeanCreationException ex) { + assertTrue(ex.getMessage().contains("nonExisting(TestBean)")); + } + } + + @Test + public void testFactoryMethodArgumentsForNonExistingMethod() { + DefaultListableBeanFactory xbf = new DefaultListableBeanFactory(); + XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(xbf); + reader.loadBeanDefinitions(new ClassPathResource("factory-methods.xml", getClass())); + try { + xbf.getBean("invalidPrototype", new TestBean()); + fail("Should have thrown BeanCreationException"); + } + catch (BeanCreationException ex) { + assertTrue(ex.getMessage().contains("nonExisting(TestBean)")); + } + } + @Test public void testCanSpecifyFactoryMethodArgumentsOnFactoryMethodPrototype() { DefaultListableBeanFactory xbf = new DefaultListableBeanFactory(); @@ -360,7 +389,9 @@ public void testFactoryMethodForJavaMailSession() { } + class MailSession { + private Properties props; private MailSession() { @@ -377,6 +408,6 @@ public static MailSession getDefaultInstance(Properties props) { } public Object getProperty(String key) { - return props.get(key); + return this.props.get(key); } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/xml/FactoryMethods.java b/spring-beans/src/test/java/org/springframework/beans/factory/xml/FactoryMethods.java index ba8c74bfe9..f450baa03e 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/xml/FactoryMethods.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/xml/FactoryMethods.java @@ -55,11 +55,11 @@ protected static FactoryMethods newInstance(TestBean tb, int num, String name) { return new FactoryMethods(tb, name, num); } - static FactoryMethods newInstance(TestBean tb, int num, Integer something) { + static ExtendedFactoryMethods newInstance(TestBean tb, int num, Integer something) { if (something != null) { throw new IllegalStateException("Should never be called with non-null value"); } - return new FactoryMethods(tb, null, num); + return new ExtendedFactoryMethods(tb, null, num); } @SuppressWarnings("unused") @@ -120,4 +120,12 @@ public void setName(String name) { this.name = name; } + + public static class ExtendedFactoryMethods extends FactoryMethods { + + ExtendedFactoryMethods(TestBean tb, String name, int num) { + super(tb, name, num); + } + } + } 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 62c5962f86..abb935865f 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 @@ -41,10 +41,12 @@ public class ProfileXmlBeanDefinitionTests { private static final String PROD_ELIGIBLE_XML = "ProfileXmlBeanDefinitionTests-prodProfile.xml"; private static final String DEV_ELIGIBLE_XML = "ProfileXmlBeanDefinitionTests-devProfile.xml"; + 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_NOT_DEV_ELIGIBLE_XML = "ProfileXmlBeanDefinitionTests-multiProfileNotDev.xml"; private static final String MULTI_ELIGIBLE_SPACE_DELIMITED_XML = "ProfileXmlBeanDefinitionTests-spaceDelimitedProfile.xml"; - private static final String UNKOWN_ELIGIBLE_XML = "ProfileXmlBeanDefinitionTests-unknownProfile.xml"; + private static final String UNKNOWN_ELIGIBLE_XML = "ProfileXmlBeanDefinitionTests-unknownProfile.xml"; private static final String DEFAULT_ELIGIBLE_XML = "ProfileXmlBeanDefinitionTests-defaultProfile.xml"; private static final String CUSTOM_DEFAULT_ELIGIBLE_XML = "ProfileXmlBeanDefinitionTests-customDefaultProfile.xml"; private static final String DEFAULT_AND_DEV_ELIGIBLE_XML = "ProfileXmlBeanDefinitionTests-defaultAndDevProfile.xml"; @@ -71,10 +73,15 @@ public void testProfilePermutations() { assertThat(beanFactoryFor(PROD_ELIGIBLE_XML, MULTI_ACTIVE), containsTargetBean()); assertThat(beanFactoryFor(DEV_ELIGIBLE_XML, NONE_ACTIVE), not(containsTargetBean())); - assertThat(beanFactoryFor(DEV_ELIGIBLE_XML, PROD_ACTIVE), not(containsTargetBean())); assertThat(beanFactoryFor(DEV_ELIGIBLE_XML, DEV_ACTIVE), containsTargetBean()); + assertThat(beanFactoryFor(DEV_ELIGIBLE_XML, PROD_ACTIVE), not(containsTargetBean())); assertThat(beanFactoryFor(DEV_ELIGIBLE_XML, MULTI_ACTIVE), containsTargetBean()); + assertThat(beanFactoryFor(NOT_DEV_ELIGIBLE_XML, NONE_ACTIVE), containsTargetBean()); + assertThat(beanFactoryFor(NOT_DEV_ELIGIBLE_XML, DEV_ACTIVE), not(containsTargetBean())); + assertThat(beanFactoryFor(NOT_DEV_ELIGIBLE_XML, PROD_ACTIVE), containsTargetBean()); + assertThat(beanFactoryFor(NOT_DEV_ELIGIBLE_XML, MULTI_ACTIVE), not(containsTargetBean())); + assertThat(beanFactoryFor(ALL_ELIGIBLE_XML, NONE_ACTIVE), containsTargetBean()); assertThat(beanFactoryFor(ALL_ELIGIBLE_XML, DEV_ACTIVE), containsTargetBean()); assertThat(beanFactoryFor(ALL_ELIGIBLE_XML, PROD_ACTIVE), containsTargetBean()); @@ -86,13 +93,19 @@ public void testProfilePermutations() { assertThat(beanFactoryFor(MULTI_ELIGIBLE_XML, PROD_ACTIVE), containsTargetBean()); assertThat(beanFactoryFor(MULTI_ELIGIBLE_XML, MULTI_ACTIVE), 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())); + assertThat(beanFactoryFor(MULTI_NOT_DEV_ELIGIBLE_XML, PROD_ACTIVE), containsTargetBean()); + assertThat(beanFactoryFor(MULTI_NOT_DEV_ELIGIBLE_XML, MULTI_ACTIVE), containsTargetBean()); + assertThat(beanFactoryFor(MULTI_ELIGIBLE_SPACE_DELIMITED_XML, NONE_ACTIVE), not(containsTargetBean())); assertThat(beanFactoryFor(MULTI_ELIGIBLE_SPACE_DELIMITED_XML, UNKNOWN_ACTIVE), not(containsTargetBean())); assertThat(beanFactoryFor(MULTI_ELIGIBLE_SPACE_DELIMITED_XML, DEV_ACTIVE), containsTargetBean()); assertThat(beanFactoryFor(MULTI_ELIGIBLE_SPACE_DELIMITED_XML, PROD_ACTIVE), containsTargetBean()); assertThat(beanFactoryFor(MULTI_ELIGIBLE_SPACE_DELIMITED_XML, MULTI_ACTIVE), containsTargetBean()); - assertThat(beanFactoryFor(UNKOWN_ELIGIBLE_XML, MULTI_ACTIVE), not(containsTargetBean())); + assertThat(beanFactoryFor(UNKNOWN_ELIGIBLE_XML, MULTI_ACTIVE), not(containsTargetBean())); } @Test diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/xml/UtilNamespaceHandlerTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/xml/UtilNamespaceHandlerTests.java index 3bb04d4c59..14efce4d5c 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/xml/UtilNamespaceHandlerTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/xml/UtilNamespaceHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author 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,14 +17,15 @@ package org.springframework.beans.factory.xml; import java.lang.reflect.Proxy; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeMap; -import java.util.Arrays; -import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Test; import org.springframework.beans.factory.config.FieldRetrievingFactoryBean; import org.springframework.beans.factory.config.PropertiesFactoryBean; @@ -35,19 +36,23 @@ import org.springframework.tests.beans.CollectingReaderEventListener; import org.springframework.tests.sample.beans.CustomEnum; import org.springframework.tests.sample.beans.TestBean; +import org.springframework.util.LinkedCaseInsensitiveMap; + +import static org.junit.Assert.*; /** * @author Rob Harrop * @author Juergen Hoeller * @author Mark Fisher */ -public class UtilNamespaceHandlerTests extends TestCase { +public class UtilNamespaceHandlerTests { private DefaultListableBeanFactory beanFactory; private CollectingReaderEventListener listener = new CollectingReaderEventListener(); - @Override + + @Before public void setUp() { this.beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this.beanFactory); @@ -55,17 +60,21 @@ public void setUp() { reader.loadBeanDefinitions(new ClassPathResource("testUtilNamespace.xml", getClass())); } - public void testConstant() throws Exception { + + @Test + public void testConstant() { Integer min = (Integer) this.beanFactory.getBean("min"); assertEquals(Integer.MIN_VALUE, min.intValue()); } - public void testConstantWithDefaultName() throws Exception { + @Test + public void testConstantWithDefaultName() { Integer max = (Integer) this.beanFactory.getBean("java.lang.Integer.MAX_VALUE"); assertEquals(Integer.MAX_VALUE, max.intValue()); } - public void testEvents() throws Exception { + @Test + public void testEvents() { ComponentDefinition propertiesComponent = this.listener.getComponentDefinition("myProperties"); assertNotNull("Event for 'myProperties' not sent", propertiesComponent); AbstractBeanDefinition propertiesBean = (AbstractBeanDefinition) propertiesComponent.getBeanDefinitions()[0]; @@ -77,30 +86,35 @@ public void testEvents() throws Exception { assertEquals("Incorrect BeanDefinition", FieldRetrievingFactoryBean.class, constantBean.getBeanClass()); } - public void testNestedProperties() throws Exception { + @Test + public void testNestedProperties() { TestBean bean = (TestBean) this.beanFactory.getBean("testBean"); Properties props = bean.getSomeProperties(); assertEquals("Incorrect property value", "bar", props.get("foo")); } - public void testPropertyPath() throws Exception { + @Test + public void testPropertyPath() { String name = (String) this.beanFactory.getBean("name"); assertEquals("Rob Harrop", name); } - public void testNestedPropertyPath() throws Exception { + @Test + public void testNestedPropertyPath() { TestBean bean = (TestBean) this.beanFactory.getBean("testBean"); assertEquals("Rob Harrop", bean.getName()); } - public void testSimpleMap() throws Exception { + @Test + public void testSimpleMap() { Map map = (Map) this.beanFactory.getBean("simpleMap"); assertEquals("bar", map.get("foo")); Map map2 = (Map) this.beanFactory.getBean("simpleMap"); assertTrue(map == map2); } - public void testScopedMap() throws Exception { + @Test + public void testScopedMap() { Map map = (Map) this.beanFactory.getBean("scopedMap"); assertEquals("bar", map.get("foo")); Map map2 = (Map) this.beanFactory.getBean("scopedMap"); @@ -108,14 +122,16 @@ public void testScopedMap() throws Exception { assertTrue(map != map2); } - public void testSimpleList() throws Exception { + @Test + public void testSimpleList() { List list = (List) this.beanFactory.getBean("simpleList"); assertEquals("Rob Harrop", list.get(0)); List list2 = (List) this.beanFactory.getBean("simpleList"); assertTrue(list == list2); } - public void testScopedList() throws Exception { + @Test + public void testScopedList() { List list = (List) this.beanFactory.getBean("scopedList"); assertEquals("Rob Harrop", list.get(0)); List list2 = (List) this.beanFactory.getBean("scopedList"); @@ -123,14 +139,16 @@ public void testScopedList() throws Exception { assertTrue(list != list2); } - public void testSimpleSet() throws Exception { + @Test + public void testSimpleSet() { Set set = (Set) this.beanFactory.getBean("simpleSet"); assertTrue(set.contains("Rob Harrop")); Set set2 = (Set) this.beanFactory.getBean("simpleSet"); assertTrue(set == set2); } - public void testScopedSet() throws Exception { + @Test + public void testScopedSet() { Set set = (Set) this.beanFactory.getBean("scopedSet"); assertTrue(set.contains("Rob Harrop")); Set set2 = (Set) this.beanFactory.getBean("scopedSet"); @@ -138,13 +156,22 @@ public void testScopedSet() throws Exception { assertTrue(set != set2); } - public void testMapWithRef() throws Exception { + @Test + public void testMapWithRef() { Map map = (Map) this.beanFactory.getBean("mapWithRef"); assertTrue(map instanceof TreeMap); assertEquals(this.beanFactory.getBean("testBean"), map.get("bean")); } - public void testNestedCollections() throws Exception { + @Test + public void testMapWithTypes() { + Map map = (Map) this.beanFactory.getBean("mapWithTypes"); + assertTrue(map instanceof LinkedCaseInsensitiveMap); + assertEquals(this.beanFactory.getBean("testBean"), map.get("bean")); + } + + @Test + public void testNestedCollections() { TestBean bean = (TestBean) this.beanFactory.getBean("nestedCollectionsBean"); List list = bean.getSomeList(); @@ -171,7 +198,8 @@ public void testNestedCollections() throws Exception { assertFalse(map == bean2.getSomeMap()); } - public void testNestedShortcutCollections() throws Exception { + @Test + public void testNestedShortcutCollections() { TestBean bean = (TestBean) this.beanFactory.getBean("nestedShortcutCollections"); assertEquals(1, bean.getStringArray().length); @@ -194,7 +222,8 @@ public void testNestedShortcutCollections() throws Exception { assertFalse(set == bean2.getSomeSet()); } - public void testNestedInCollections() throws Exception { + @Test + public void testNestedInCollections() { TestBean bean = (TestBean) this.beanFactory.getBean("nestedCustomTagBean"); List list = bean.getSomeList(); @@ -219,7 +248,8 @@ public void testNestedInCollections() throws Exception { assertFalse(map == bean2.getSomeMap()); } - public void testCircularCollections() throws Exception { + @Test + public void testCircularCollections() { TestBean bean = (TestBean) this.beanFactory.getBean("circularCollectionsBean"); List list = bean.getSomeList(); @@ -235,7 +265,8 @@ public void testCircularCollections() throws Exception { assertEquals(bean, map.get("foo")); } - public void testCircularCollectionBeansStartingWithList() throws Exception { + @Test + public void testCircularCollectionBeansStartingWithList() { this.beanFactory.getBean("circularList"); TestBean bean = (TestBean) this.beanFactory.getBean("circularCollectionBeansBean"); @@ -255,7 +286,8 @@ public void testCircularCollectionBeansStartingWithList() throws Exception { assertEquals(bean, map.get("foo")); } - public void testCircularCollectionBeansStartingWithSet() throws Exception { + @Test + public void testCircularCollectionBeansStartingWithSet() { this.beanFactory.getBean("circularSet"); TestBean bean = (TestBean) this.beanFactory.getBean("circularCollectionBeansBean"); @@ -275,7 +307,8 @@ public void testCircularCollectionBeansStartingWithSet() throws Exception { assertEquals(bean, map.get("foo")); } - public void testCircularCollectionBeansStartingWithMap() throws Exception { + @Test + public void testCircularCollectionBeansStartingWithMap() { this.beanFactory.getBean("circularMap"); TestBean bean = (TestBean) this.beanFactory.getBean("circularCollectionBeansBean"); @@ -295,12 +328,14 @@ public void testCircularCollectionBeansStartingWithMap() throws Exception { assertEquals(bean, map.get("foo")); } - public void testNestedInConstructor() throws Exception { + @Test + public void testNestedInConstructor() { TestBean bean = (TestBean) this.beanFactory.getBean("constructedTestBean"); assertEquals("Rob Harrop", bean.getName()); } - public void testLoadProperties() throws Exception { + @Test + public void testLoadProperties() { Properties props = (Properties) this.beanFactory.getBean("myProperties"); assertEquals("Incorrect property value", "bar", props.get("foo")); assertEquals("Incorrect property value", null, props.get("foo2")); @@ -308,7 +343,8 @@ public void testLoadProperties() throws Exception { assertTrue(props == props2); } - public void testScopedProperties() throws Exception { + @Test + public void testScopedProperties() { Properties props = (Properties) this.beanFactory.getBean("myScopedProperties"); assertEquals("Incorrect property value", "bar", props.get("foo")); assertEquals("Incorrect property value", null, props.get("foo2")); @@ -318,30 +354,35 @@ public void testScopedProperties() throws Exception { assertTrue(props != props2); } - public void testLocalProperties() throws Exception { + @Test + public void testLocalProperties() { Properties props = (Properties) this.beanFactory.getBean("myLocalProperties"); assertEquals("Incorrect property value", null, props.get("foo")); assertEquals("Incorrect property value", "bar2", props.get("foo2")); } - public void testMergedProperties() throws Exception { + @Test + public void testMergedProperties() { Properties props = (Properties) this.beanFactory.getBean("myMergedProperties"); assertEquals("Incorrect property value", "bar", props.get("foo")); assertEquals("Incorrect property value", "bar2", props.get("foo2")); } + @Test public void testLocalOverrideDefault() { Properties props = (Properties) this.beanFactory.getBean("defaultLocalOverrideProperties"); assertEquals("Incorrect property value", "bar", props.get("foo")); assertEquals("Incorrect property value", "local2", props.get("foo2")); } + @Test public void testLocalOverrideFalse() { Properties props = (Properties) this.beanFactory.getBean("falseLocalOverrideProperties"); assertEquals("Incorrect property value", "bar", props.get("foo")); assertEquals("Incorrect property value", "local2", props.get("foo2")); } + @Test public void testLocalOverrideTrue() { Properties props = (Properties) this.beanFactory.getBean("trueLocalOverrideProperties"); assertEquals("Incorrect property value", "local", props.get("foo")); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReaderTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReaderTests.java index c5da99f0e4..04f39e6909 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReaderTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReaderTests.java @@ -29,7 +29,7 @@ import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; import org.springframework.tests.sample.beans.TestBean; - +import org.springframework.util.ObjectUtils; /** * @author Rick Evans @@ -38,13 +38,13 @@ public class XmlBeanDefinitionReaderTests extends TestCase { public void testSetParserClassSunnyDay() { - SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();; + SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); new XmlBeanDefinitionReader(registry).setDocumentReaderClass(DefaultBeanDefinitionDocumentReader.class); } public void testSetParserClassToNull() { try { - SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();; + SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); new XmlBeanDefinitionReader(registry).setDocumentReaderClass(null); fail("Should have thrown IllegalArgumentException (null parserClass)"); } @@ -54,7 +54,7 @@ public void testSetParserClassToNull() { public void testSetParserClassToUnsupportedParserType() throws Exception { try { - SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();; + SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); new XmlBeanDefinitionReader(registry).setDocumentReaderClass(String.class); fail("Should have thrown IllegalArgumentException (unsupported parserClass)"); } @@ -64,7 +64,7 @@ public void testSetParserClassToUnsupportedParserType() throws Exception { public void testWithOpenInputStream() { try { - SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();; + SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); Resource resource = new InputStreamResource(getClass().getResourceAsStream("test.xml")); new XmlBeanDefinitionReader(registry).loadBeanDefinitions(resource); fail("Should have thrown BeanDefinitionStoreException (can't determine validation mode)"); @@ -74,7 +74,7 @@ public void testWithOpenInputStream() { } public void testWithOpenInputStreamAndExplicitValidationMode() { - SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();; + SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); Resource resource = new InputStreamResource(getClass().getResourceAsStream("test.xml")); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry); reader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_DTD); @@ -83,14 +83,14 @@ public void testWithOpenInputStreamAndExplicitValidationMode() { } public void testWithImport() { - SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();; + SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); Resource resource = new ClassPathResource("import.xml", getClass()); new XmlBeanDefinitionReader(registry).loadBeanDefinitions(resource); testBeanDefinitions(registry); } public void testWithWildcardImport() { - SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();; + SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); Resource resource = new ClassPathResource("importPattern.xml", getClass()); new XmlBeanDefinitionReader(registry).loadBeanDefinitions(resource); testBeanDefinitions(registry); @@ -98,7 +98,7 @@ public void testWithWildcardImport() { public void testWithInputSource() { try { - SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();; + SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); InputSource resource = new InputSource(getClass().getResourceAsStream("test.xml")); new XmlBeanDefinitionReader(registry).loadBeanDefinitions(resource); fail("Should have thrown BeanDefinitionStoreException (can't determine validation mode)"); @@ -108,7 +108,7 @@ public void testWithInputSource() { } public void testWithInputSourceAndExplicitValidationMode() { - SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();; + SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); InputSource resource = new InputSource(getClass().getResourceAsStream("test.xml")); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry); reader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_DTD); @@ -117,7 +117,7 @@ public void testWithInputSourceAndExplicitValidationMode() { } public void testWithFreshInputStream() { - SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();; + SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); Resource resource = new ClassPathResource("test.xml", getClass()); new XmlBeanDefinitionReader(registry).loadBeanDefinitions(resource); testBeanDefinitions(registry); @@ -133,9 +133,10 @@ private void testBeanDefinitions(BeanDefinitionRegistry registry) { assertEquals(TestBean.class.getName(), registry.getBeanDefinition("rod").getBeanClassName()); assertEquals(TestBean.class.getName(), registry.getBeanDefinition("aliased").getBeanClassName()); assertTrue(registry.isAlias("youralias")); - assertEquals(2, registry.getAliases("aliased").length); - assertEquals("myalias", registry.getAliases("aliased")[0]); - assertEquals("youralias", registry.getAliases("aliased")[1]); + String[] aliases = registry.getAliases("aliased"); + assertEquals(2, aliases.length); + assertTrue(ObjectUtils.containsElement(aliases, "myalias")); + assertTrue(ObjectUtils.containsElement(aliases, "youralias")); } public void testDtdValidationAutodetect() throws Exception { @@ -147,7 +148,7 @@ public void testXsdValidationAutodetect() throws Exception { } private void doTestValidation(String resourceName) throws Exception { - DefaultListableBeanFactory factory = new DefaultListableBeanFactory();; + DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); Resource resource = new ClassPathResource(resourceName, getClass()); new XmlBeanDefinitionReader(factory).loadBeanDefinitions(resource); TestBean bean = (TestBean) factory.getBean("testBean"); diff --git a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/CustomEditorTests.java b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/CustomEditorTests.java index af590e3936..c14e52fd56 100644 --- a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/CustomEditorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/CustomEditorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,7 +59,6 @@ * @author Rob Harrop * @author Arjen Poutsma * @author Chris Beams - * * @since 10.06.2003 */ public class CustomEditorTests { @@ -302,8 +301,8 @@ public void testCustomBooleanEditorWithAllowEmpty() { @Test public void testCustomBooleanEditorWithSpecialTrueAndFalseStrings() throws Exception { - final String trueString = "pechorin"; - final String falseString = "nash"; + String trueString = "pechorin"; + String falseString = "nash"; CustomBooleanEditor editor = new CustomBooleanEditor(trueString, falseString, false); @@ -320,6 +319,14 @@ public void testCustomBooleanEditorWithSpecialTrueAndFalseStrings() throws Excep editor.setAsText(falseString.toUpperCase()); assertFalse(((Boolean) editor.getValue()).booleanValue()); assertEquals(falseString, editor.getAsText()); + + try { + editor.setAsText(null); + fail("Should have thrown IllegalArgumentException"); + } + catch (IllegalArgumentException ex) { + // expected + } } @Test @@ -423,7 +430,7 @@ public void testCustomNumberEditorWithoutAllowEmpty() { assertTrue("Correct bigDecimal value", new BigDecimal("4.5").equals(tb.getBigDecimal())); } - @Test(expected=IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void testCustomNumberEditorCtorWithNullNumberType() throws Exception { new CustomNumberEditor(null, true); } @@ -543,7 +550,7 @@ public void testCharacterEditorWithAllowEmpty() { assertNull(cb.getMyCharacter()); } - @Test(expected=IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void testCharacterEditorSetAsTextWithStringLongerThanOneCharacter() throws Exception { PropertyEditor charEditor = new CharacterEditor(false); charEditor.setAsText("ColdWaterCanyon"); @@ -562,7 +569,7 @@ public void testCharacterEditorGetAsTextReturnsEmptyStringIfValueIsNull() throws assertEquals(" ", charEditor.getAsText()); } - @Test(expected=IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void testCharacterEditorSetAsTextWithNullNotAllowingEmptyAsNull() throws Exception { PropertyEditor charEditor = new CharacterEditor(false); charEditor.setAsText(null); @@ -583,7 +590,7 @@ public void testClassEditor() { assertEquals("", classEditor.getAsText()); } - @Test(expected=IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void testClassEditorWithNonExistentClass() throws Exception { PropertyEditor classEditor = new ClassEditor(); classEditor.setAsText("hairdresser.on.Fire"); @@ -685,26 +692,40 @@ public void testPatternEditor() { @Test public void testCustomBooleanEditor() { CustomBooleanEditor editor = new CustomBooleanEditor(false); + editor.setAsText("true"); assertEquals(Boolean.TRUE, editor.getValue()); assertEquals("true", editor.getAsText()); + editor.setAsText("false"); assertEquals(Boolean.FALSE, editor.getValue()); assertEquals("false", editor.getAsText()); + editor.setValue(null); assertEquals(null, editor.getValue()); assertEquals("", editor.getAsText()); + + try { + editor.setAsText(null); + fail("Should have thrown IllegalArgumentException"); + } + catch (IllegalArgumentException ex) { + // expected + } } @Test public void testCustomBooleanEditorWithEmptyAsNull() { CustomBooleanEditor editor = new CustomBooleanEditor(true); + editor.setAsText("true"); assertEquals(Boolean.TRUE, editor.getValue()); assertEquals("true", editor.getAsText()); + editor.setAsText("false"); assertEquals(Boolean.FALSE, editor.getValue()); assertEquals("false", editor.getAsText()); + editor.setValue(null); assertEquals(null, editor.getValue()); assertEquals("", editor.getAsText()); @@ -750,7 +771,7 @@ public void testCustomDateEditorWithExactDateLength() { } catch (IllegalArgumentException ex) { // expected - assertTrue(ex.getMessage().indexOf("10") != -1); + assertTrue(ex.getMessage().contains("10")); } } diff --git a/spring-beans/src/test/java/org/springframework/tests/beans/CollectingReaderEventListener.java b/spring-beans/src/test/java/org/springframework/tests/beans/CollectingReaderEventListener.java index b581a05f03..b749d868ed 100644 --- a/spring-beans/src/test/java/org/springframework/tests/beans/CollectingReaderEventListener.java +++ b/spring-beans/src/test/java/org/springframework/tests/beans/CollectingReaderEventListener.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. @@ -36,13 +36,15 @@ */ public class CollectingReaderEventListener implements ReaderEventListener { - private final List defaults = new LinkedList(); + private final List defaults = new LinkedList(); - private final Map componentDefinitions = new LinkedHashMap<>(8); + private final Map componentDefinitions = + new LinkedHashMap(8); - private final Map aliasMap = new LinkedHashMap<>(8); + private final Map> aliasMap = + new LinkedHashMap>(8); - private final List imports = new LinkedList(); + private final List imports = new LinkedList(); @Override @@ -50,7 +52,7 @@ public void defaultsRegistered(DefaultsDefinition defaultsDefinition) { this.defaults.add(defaultsDefinition); } - public List getDefaults() { + public List getDefaults() { return Collections.unmodifiableList(this.defaults); } @@ -60,27 +62,27 @@ public void componentRegistered(ComponentDefinition componentDefinition) { } public ComponentDefinition getComponentDefinition(String name) { - return (ComponentDefinition) this.componentDefinitions.get(name); + return this.componentDefinitions.get(name); } public ComponentDefinition[] getComponentDefinitions() { - Collection collection = this.componentDefinitions.values(); - return (ComponentDefinition[]) collection.toArray(new ComponentDefinition[collection.size()]); + Collection collection = this.componentDefinitions.values(); + return collection.toArray(new ComponentDefinition[collection.size()]); } @Override public void aliasRegistered(AliasDefinition aliasDefinition) { - List aliases = (List) this.aliasMap.get(aliasDefinition.getBeanName()); - if(aliases == null) { - aliases = new ArrayList(); + List aliases = this.aliasMap.get(aliasDefinition.getBeanName()); + if (aliases == null) { + aliases = new ArrayList(); this.aliasMap.put(aliasDefinition.getBeanName(), aliases); } aliases.add(aliasDefinition); } - public List getAliases(String beanName) { - List aliases = (List) this.aliasMap.get(beanName); - return aliases == null ? null : Collections.unmodifiableList(aliases); + public List getAliases(String beanName) { + List aliases = this.aliasMap.get(beanName); + return (aliases != null ? Collections.unmodifiableList(aliases) : null); } @Override @@ -88,7 +90,7 @@ public void importProcessed(ImportDefinition importDefinition) { this.imports.add(importDefinition); } - public List getImports() { + public List getImports() { return Collections.unmodifiableList(this.imports); } diff --git a/spring-beans/src/test/java/org/springframework/tests/sample/beans/DerivedTestBean.java b/spring-beans/src/test/java/org/springframework/tests/sample/beans/DerivedTestBean.java index 91416208a2..d12db5b7ad 100644 --- a/spring-beans/src/test/java/org/springframework/tests/sample/beans/DerivedTestBean.java +++ b/spring-beans/src/test/java/org/springframework/tests/sample/beans/DerivedTestBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,6 +71,11 @@ public void setSpouseRef(String name) { setSpouse(new TestBean(name)); } + @Override + public TestBean getSpouse() { + return (TestBean) super.getSpouse(); + } + public void initialize() { this.initialized = true; 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 cb276da9cb..5d1bcad7ac 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-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. @@ -76,13 +76,13 @@ public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOt private Float myFloat = new Float(0.0); - private Collection friends = new LinkedList<>(); + private Collection friends = new LinkedList(); - private Set someSet = new HashSet<>(); + private Set someSet = new HashSet(); - private Map someMap = new HashMap<>(); + private Map someMap = new HashMap(); - private List someList = new ArrayList<>(); + private List someList = new ArrayList(); private Properties someProperties = new Properties(); @@ -255,10 +255,12 @@ public void setStringArray(String[] stringArray) { this.stringArray = stringArray; } + @Override public Integer[] getSomeIntegerArray() { return someIntegerArray; } + @Override public void setSomeIntegerArray(Integer[] someIntegerArray) { this.someIntegerArray = someIntegerArray; } @@ -461,6 +463,7 @@ public boolean wasDestroyed() { } + @Override public boolean equals(Object other) { if (this == other) { return true; @@ -472,6 +475,7 @@ public boolean equals(Object other) { return (ObjectUtils.nullSafeEquals(this.name, tb2.name) && this.age == tb2.age); } + @Override public int hashCode() { return this.age; } @@ -486,6 +490,7 @@ public int compareTo(Object other) { } } + @Override public String toString() { return this.name; } diff --git a/spring-beans/src/test/resources/org/springframework/beans/factory/xml/ProfileXmlBeanDefinitionTests-multiProfileNotDev.xml b/spring-beans/src/test/resources/org/springframework/beans/factory/xml/ProfileXmlBeanDefinitionTests-multiProfileNotDev.xml new file mode 100644 index 0000000000..b0a246c4a2 --- /dev/null +++ b/spring-beans/src/test/resources/org/springframework/beans/factory/xml/ProfileXmlBeanDefinitionTests-multiProfileNotDev.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/spring-beans/src/test/resources/org/springframework/beans/factory/xml/ProfileXmlBeanDefinitionTests-notDevProfile.xml b/spring-beans/src/test/resources/org/springframework/beans/factory/xml/ProfileXmlBeanDefinitionTests-notDevProfile.xml new file mode 100644 index 0000000000..06ac54a37a --- /dev/null +++ b/spring-beans/src/test/resources/org/springframework/beans/factory/xml/ProfileXmlBeanDefinitionTests-notDevProfile.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/spring-beans/src/test/resources/org/springframework/beans/factory/xml/collections.xml b/spring-beans/src/test/resources/org/springframework/beans/factory/xml/collections.xml index 974ae6c3d9..73294dfbae 100644 --- a/spring-beans/src/test/resources/org/springframework/beans/factory/xml/collections.xml +++ b/spring-beans/src/test/resources/org/springframework/beans/factory/xml/collections.xml @@ -35,11 +35,10 @@ - + Jenny 30 - diff --git a/spring-beans/src/test/resources/org/springframework/beans/factory/xml/factory-methods.xml b/spring-beans/src/test/resources/org/springframework/beans/factory/xml/factory-methods.xml index 4ff20b3d59..ffcd3f7da9 100644 --- a/spring-beans/src/test/resources/org/springframework/beans/factory/xml/factory-methods.xml +++ b/spring-beans/src/test/resources/org/springframework/beans/factory/xml/factory-methods.xml @@ -84,6 +84,11 @@ testBeanOnlyPrototypeDISetterString + + + + 27 @@ -120,9 +125,7 @@ 33 - + diff --git a/spring-beans/src/test/resources/org/springframework/beans/factory/xml/testUtilNamespace.xml b/spring-beans/src/test/resources/org/springframework/beans/factory/xml/testUtilNamespace.xml index 25f20439fa..2b71bca48f 100644 --- a/spring-beans/src/test/resources/org/springframework/beans/factory/xml/testUtilNamespace.xml +++ b/spring-beans/src/test/resources/org/springframework/beans/factory/xml/testUtilNamespace.xml @@ -15,7 +15,7 @@ + "/> @@ -49,6 +49,11 @@ + + + + Rob Harrop diff --git a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCacheManager.java b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCacheManager.java index 80ca887b9c..316ff15d09 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCacheManager.java +++ b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCacheManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,7 +46,7 @@ public EhCacheCacheManager() { } /** - * Create a new EhCacheCacheManager for the given backing EhCache. + * Create a new EhCacheCacheManager for the given backing EhCache CacheManager. * @param cacheManager the backing EhCache {@link net.sf.ehcache.CacheManager} */ public EhCacheCacheManager(net.sf.ehcache.CacheManager cacheManager) { @@ -92,8 +92,8 @@ public Cache getCache(String name) { // (in case the cache was added at runtime) Ehcache ehcache = this.cacheManager.getEhcache(name); if (ehcache != null) { - cache = new EhCacheCache(ehcache); - addCache(cache); + addCache(new EhCacheCache(ehcache)); + cache = super.getCache(name); // potentially decorated } } return cache; diff --git a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheFactoryBean.java b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheFactoryBean.java index dcec4047ee..4806cddb15 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheFactoryBean.java +++ b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheFactoryBean.java @@ -38,6 +38,7 @@ import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; /** * {@link FactoryBean} that creates a named EhCache {@link net.sf.ehcache.Cache} instance @@ -64,6 +65,11 @@ */ public class EhCacheFactoryBean implements FactoryBean, BeanNameAware, InitializingBean { + // EhCache's setStatisticsEnabled(boolean) available? Not anymore as of EhCache 2.7... + private static final boolean setStatisticsAvailable = + ClassUtils.hasMethod(Ehcache.class, "setStatisticsEnabled", boolean.class); + + protected final Log logger = LogFactory.getLog(getClass()); private CacheManager cacheManager; @@ -275,7 +281,9 @@ public void setCacheEventListeners(Set cacheEventListeners) /** * Set whether to enable EhCache statistics on this cache. - * @see net.sf.ehcache.Cache#setStatisticsEnabled + *

Note: As of EhCache 2.7, statistics are enabled by default, and cannot be turned off. + * This setter therefore has no effect in such a scenario. + * @see net.sf.ehcache.Ehcache#setStatisticsEnabled */ public void setStatisticsEnabled(boolean statisticsEnabled) { this.statisticsEnabled = statisticsEnabled; @@ -283,7 +291,9 @@ public void setStatisticsEnabled(boolean statisticsEnabled) { /** * Set whether to enable EhCache's sampled statistics on this cache. - * @see net.sf.ehcache.Cache#setSampledStatisticsEnabled + *

Note: As of EhCache 2.7, statistics are enabled by default, and cannot be turned off. + * This setter therefore has no effect in such a scenario. + * @see net.sf.ehcache.Ehcache#setSampledStatisticsEnabled */ public void setSampledStatisticsEnabled(boolean sampledStatisticsEnabled) { this.sampledStatisticsEnabled = sampledStatisticsEnabled; @@ -316,44 +326,55 @@ public void afterPropertiesSet() throws CacheException, IOException { this.cacheName = this.beanName; } - // Fetch cache region: If none with the given name exists, - // create one on the fly. - Ehcache rawCache; - if (this.cacheManager.cacheExists(this.cacheName)) { - if (logger.isDebugEnabled()) { - logger.debug("Using existing EhCache cache region '" + this.cacheName + "'"); + synchronized (this.cacheManager) { + // Fetch cache region: If none with the given name exists, + // create one on the fly. + Ehcache rawCache; + boolean cacheExists = this.cacheManager.cacheExists(this.cacheName); + + if (cacheExists) { + if (logger.isDebugEnabled()) { + logger.debug("Using existing EhCache cache region '" + this.cacheName + "'"); + } + rawCache = this.cacheManager.getEhcache(this.cacheName); } - rawCache = this.cacheManager.getEhcache(this.cacheName); - } - else { - if (logger.isDebugEnabled()) { - logger.debug("Creating new EhCache cache region '" + this.cacheName + "'"); + else { + if (logger.isDebugEnabled()) { + logger.debug("Creating new EhCache cache region '" + this.cacheName + "'"); + } + rawCache = createCache(); } - rawCache = createCache(); - this.cacheManager.addCache(rawCache); - } - if (this.cacheEventListeners != null) { - for (CacheEventListener listener : this.cacheEventListeners) { - rawCache.getCacheEventNotificationService().registerListener(listener); + if (this.cacheEventListeners != null) { + for (CacheEventListener listener : this.cacheEventListeners) { + rawCache.getCacheEventNotificationService().registerListener(listener); + } + } + + // Needs to happen after listener registration but before setStatisticsEnabled + if (!cacheExists) { + this.cacheManager.addCache(rawCache); } - } - if (this.statisticsEnabled) { - rawCache.setStatisticsEnabled(true); - } - if (this.sampledStatisticsEnabled) { - rawCache.setSampledStatisticsEnabled(true); - } - if (this.disabled) { - rawCache.setDisabled(true); - } - // Decorate cache if necessary. - Ehcache decoratedCache = decorateCache(rawCache); - if (decoratedCache != rawCache) { - this.cacheManager.replaceCacheWithDecoratedCache(rawCache, decoratedCache); + // Only necessary on EhCache <2.7: As of 2.7, statistics are on by default. + if (setStatisticsAvailable) { + if (this.statisticsEnabled) { + rawCache.setStatisticsEnabled(true); + } + if (this.sampledStatisticsEnabled) { + rawCache.setSampledStatisticsEnabled(true); + } + } + if (this.disabled) { + rawCache.setDisabled(true); + } + + Ehcache decoratedCache = decorateCache(rawCache); + if (decoratedCache != rawCache) { + this.cacheManager.replaceCacheWithDecoratedCache(rawCache, decoratedCache); + } + this.cache = decoratedCache; } - this.cache = decoratedCache; } /** diff --git a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java index a94c1fe2cb..b2e1aa6cdc 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java +++ b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author 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 @@ * also necessary for loading EhCache configuration from a non-default config location. * *

Note: As of Spring 3.0, Spring's EhCache support requires EhCache 1.3 or higher. - * As of Spring 3.2, we recommend using EhCache 2.1 or higher. + * As of Spring 3.2, we recommend using EhCache 2.5 or higher. * * @author Dmitriy Kopylenko * @author Juergen Hoeller @@ -106,7 +106,7 @@ public void setCacheManagerName(String cacheManagerName) { } - public void afterPropertiesSet() throws IOException, CacheException { + public void afterPropertiesSet() throws CacheException, IOException { logger.info("Initializing EhCache CacheManager"); InputStream is = (this.configLocation != null ? this.configLocation.getInputStream() : null); try { 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 d47e3dcb92..7c5ede5ca8 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-2012 the original author or authors. + * Copyright 2002-2015 the original author 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,10 @@ protected Object toStoreValue(Object userValue) { @SuppressWarnings("serial") private static class NullHolder implements Serializable { + + private Object readResolve() { + return NULL_HOLDER; + } } } 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 e06d8492d2..181b9bf7c8 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-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -110,8 +110,8 @@ public Cache getCache(String name) { // (in case the cache was added at runtime) javax.cache.Cache jcache = this.cacheManager.getCache(name); if (jcache != null) { - cache = new JCacheCache(jcache, this.allowNullValues); - addCache(cache); + addCache(new JCacheCache(jcache, this.allowNullValues)); + cache = super.getCache(name); // potentially decorated } } return cache; diff --git a/spring-context-support/src/main/java/org/springframework/cache/transaction/TransactionAwareCacheManagerProxy.java b/spring-context-support/src/main/java/org/springframework/cache/transaction/TransactionAwareCacheManagerProxy.java index efcfd8f54c..ea981c6a03 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/transaction/TransactionAwareCacheManagerProxy.java +++ b/spring-context-support/src/main/java/org/springframework/cache/transaction/TransactionAwareCacheManagerProxy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,7 +67,7 @@ public void setTargetCacheManager(CacheManager targetCacheManager) { public void afterPropertiesSet() { if (this.targetCacheManager == null) { - throw new IllegalStateException("'targetCacheManager' is required"); + throw new IllegalArgumentException("Property 'targetCacheManager' is required"); } } diff --git a/spring-context-support/src/main/java/org/springframework/mail/javamail/ConfigurableMimeFileTypeMap.java b/spring-context-support/src/main/java/org/springframework/mail/javamail/ConfigurableMimeFileTypeMap.java index ac683c5243..7ec61d262c 100644 --- a/spring-context-support/src/main/java/org/springframework/mail/javamail/ConfigurableMimeFileTypeMap.java +++ b/spring-context-support/src/main/java/org/springframework/mail/javamail/ConfigurableMimeFileTypeMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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 @@ *

The mapping file should be in the following format, as specified by the * Java Activation Framework: * - *

+ * 
  * # map text/html to .htm and .html files
  * text/html  html htm HTML HTM
* @@ -93,7 +93,7 @@ public void setMappingLocation(Resource mappingLocation) { * Java Activation Framework, for example:
* {@code text/html html htm HTML HTM} */ - public void setMappings(String[] mappings) { + public void setMappings(String... mappings) { this.mappings = mappings; } diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/commonj/TimerManagerTaskScheduler.java b/spring-context-support/src/main/java/org/springframework/scheduling/commonj/TimerManagerTaskScheduler.java index 76714ce5f2..81c26787b8 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/commonj/TimerManagerTaskScheduler.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/commonj/TimerManagerTaskScheduler.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. @@ -126,7 +126,7 @@ public boolean cancel(boolean mayInterruptIfRunning) { } public long getDelay(TimeUnit unit) { - return unit.convert(System.currentTimeMillis() - this.timer.getScheduledExecutionTime(), TimeUnit.MILLISECONDS); + return unit.convert(this.timer.getScheduledExecutionTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } public int compareTo(Delayed other) { diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java b/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java index 4d1518dfa9..4995752109 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,9 @@ package org.springframework.scheduling.commonj; import java.util.Collection; +import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; -import java.util.concurrent.Callable; - import javax.naming.NamingException; import commonj.work.Work; @@ -54,10 +53,8 @@ * server's JNDI environment, as defined in the server's management console. * *

Note: At the time of this writing, the CommonJ WorkManager facility - * is only supported on IBM WebSphere 6.0+ and BEA WebLogic 9.0+, + * is only supported on IBM WebSphere 6.1+ and BEA WebLogic 9.0+, * despite being such a crucial API for an application server. - * (There is a similar facility available on WebSphere 5.1 Enterprise, - * though, which we will discuss below.) * *

On JBoss and GlassFish, a similar facility is available through * the JCA WorkManager. See the @@ -80,8 +77,7 @@ public class WorkManagerTaskExecutor extends JndiLocatorSupport /** * Specify the CommonJ WorkManager to delegate to. - *

Alternatively, you can also specify the JNDI name - * of the target WorkManager. + *

Alternatively, you can also specify the JNDI name of the target WorkManager. * @see #setWorkManagerName */ public void setWorkManager(WorkManager workManager) { @@ -90,9 +86,8 @@ public void setWorkManager(WorkManager workManager) { /** * Set the JNDI name of the CommonJ WorkManager. - *

This can either be a fully qualified JNDI name, - * or the JNDI name relative to the current environment - * naming context if "resourceRef" is set to "true". + *

This can either be a fully qualified JNDI name, or the JNDI name relative + * to the current environment naming context if "resourceRef" is set to "true". * @see #setWorkManager * @see #setResourceRef */ @@ -170,27 +165,19 @@ public boolean prefersShortLivedTasks() { // Implementation of the CommonJ WorkManager interface //------------------------------------------------------------------------- - public WorkItem schedule(Work work) - throws WorkException, IllegalArgumentException { - + public WorkItem schedule(Work work) throws WorkException, IllegalArgumentException { return this.workManager.schedule(work); } - public WorkItem schedule(Work work, WorkListener workListener) - throws WorkException, IllegalArgumentException { - + public WorkItem schedule(Work work, WorkListener workListener) throws WorkException { return this.workManager.schedule(work, workListener); } - public boolean waitForAll(Collection workItems, long timeout) - throws InterruptedException, IllegalArgumentException { - + public boolean waitForAll(Collection workItems, long timeout) throws InterruptedException { return this.workManager.waitForAll(workItems, timeout); } - public Collection waitForAny(Collection workItems, long timeout) - throws InterruptedException, IllegalArgumentException { - + public Collection waitForAny(Collection workItems, long timeout) throws InterruptedException { return this.workManager.waitForAny(workItems, timeout); } diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/AdaptableJobFactory.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/AdaptableJobFactory.java index aa40358ca9..5d4bf87d8b 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/AdaptableJobFactory.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/AdaptableJobFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ * JobFactory implementation that supports {@link java.lang.Runnable} * objects as well as standard Quartz {@link org.quartz.Job} instances. * - *

Compatible with Quartz 1.5+ as well as Quartz 2.0/2.1, as of Spring 3.1. + *

Compatible with Quartz 1.5+ as well as Quartz 2.0-2.2, as of Spring 3.2. * * @author Juergen Hoeller * @since 2.0 diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/CronTriggerBean.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/CronTriggerBean.java index c5e1c070b8..8ca82b4918 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/CronTriggerBean.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/CronTriggerBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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 @@ * Use Quartz 2.0's native {@code JobDetailImpl} class or the new Quartz 2.0 * builder API instead. Alternatively, switch to Spring's {@link CronTriggerFactoryBean} * which largely is a drop-in replacement for this class and its properties and - * consistently works against Quartz 1.x as well as Quartz 2.0/2.1. + * consistently works against Quartz 1.x as well as Quartz 2.x. * * @author Juergen Hoeller * @since 18.02.2004 @@ -73,7 +73,7 @@ public class CronTriggerBean extends CronTrigger private String beanName; - private long startDelay; + private long startDelay = 0; /** @@ -108,9 +108,9 @@ public void setMisfireInstructionName(String constantName) { * @see SchedulerFactoryBean#setTriggerListeners * @see org.quartz.TriggerListener#getName */ - public void setTriggerListenerNames(String[] names) { - for (int i = 0; i < names.length; i++) { - addTriggerListener(names[i]); + public void setTriggerListenerNames(String... names) { + for (String name : names) { + addTriggerListener(name); } } @@ -148,19 +148,19 @@ public void setBeanName(String beanName) { } + /** + * Note that this method's declaration of an Exception is deprecated + * and will be removed in the Spring 4.0 line. + */ public void afterPropertiesSet() throws Exception { - if (this.startDelay > 0) { - setStartTime(new Date(System.currentTimeMillis() + this.startDelay)); - } - if (getName() == null) { setName(this.beanName); } if (getGroup() == null) { setGroup(Scheduler.DEFAULT_GROUP); } - if (getStartTime() == null) { - setStartTime(new Date()); + if (this.startDelay > 0 || getStartTime() == null) { + setStartTime(new Date(System.currentTimeMillis() + this.startDelay)); } if (getTimeZone() == null) { setTimeZone(TimeZone.getDefault()); diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/CronTriggerFactoryBean.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/CronTriggerFactoryBean.java index 101c3fdf04..03fc938b3b 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/CronTriggerFactoryBean.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/CronTriggerFactoryBean.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. @@ -34,6 +34,7 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.core.Constants; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; /** @@ -49,7 +50,7 @@ * to automatically register a trigger for the corresponding JobDetail, * instead of registering the JobDetail separately. * - *

NOTE: This FactoryBean works against both Quartz 1.x and Quartz 2.0/2.1, + *

NOTE: This FactoryBean works against both Quartz 1.x and Quartz 2.x, * in contrast to the older {@link CronTriggerBean} class. * * @author Juergen Hoeller @@ -78,16 +79,20 @@ public class CronTriggerFactoryBean implements FactoryBean, BeanNam private Date startTime; - private long startDelay; + private long startDelay = 0; private String cronExpression; private TimeZone timeZone; + private String calendarName; + private int priority; private int misfireInstruction; + private String description; + private String beanName; private CronTrigger cronTrigger; @@ -141,6 +146,15 @@ public void setJobDataAsMap(Map jobDataAsMap) { this.jobDataMap.putAll(jobDataAsMap); } + /** + * Set a specific start time for the trigger. + *

Note that a dynamically computed {@link #setStartDelay} specification + * overrides a static timestamp set here. + */ + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + /** * Set the start delay in milliseconds. *

The start delay is added to the current system time (when the bean starts) @@ -165,6 +179,13 @@ public void setTimeZone(TimeZone timeZone) { this.timeZone = timeZone; } + /** + * Associate a specific calendar with this cron trigger. + */ + public void setCalendarName(String calendarName) { + this.calendarName = calendarName; + } + /** * Specify the priority of this trigger. */ @@ -191,6 +212,13 @@ public void setMisfireInstructionName(String constantName) { this.misfireInstruction = constants.asNumber(constantName).intValue(); } + /** + * Associate a textual description with this trigger. + */ + public void setDescription(String description) { + this.description = description; + } + public void setBeanName(String beanName) { this.beanName = beanName; } @@ -206,12 +234,9 @@ public void afterPropertiesSet() { if (this.jobDetail != null) { this.jobDataMap.put(JobDetailAwareTrigger.JOB_DETAIL_KEY, this.jobDetail); } - if (this.startDelay > 0) { + if (this.startDelay > 0 || this.startTime == null) { this.startTime = new Date(System.currentTimeMillis() + this.startDelay); } - else if (this.startTime == null) { - this.startTime = new Date(); - } if (this.timeZone == null) { this.timeZone = TimeZone.getDefault(); } @@ -220,20 +245,24 @@ else if (this.startTime == null) { CronTriggerImpl cti = new CronTriggerImpl(); cti.setName(this.name); cti.setGroup(this.group); - cti.setJobKey(this.jobDetail.getKey()); + if (this.jobDetail != null) { + cti.setJobKey(this.jobDetail.getKey()); + } cti.setJobDataMap(this.jobDataMap); cti.setStartTime(this.startTime); cti.setCronExpression(this.cronExpression); cti.setTimeZone(this.timeZone); + cti.setCalendarName(this.calendarName); cti.setPriority(this.priority); cti.setMisfireInstruction(this.misfireInstruction); + cti.setDescription(this.description); this.cronTrigger = cti; */ - Class cronTriggerClass; + Class cronTriggerClass; Method jobKeyMethod; try { - cronTriggerClass = getClass().getClassLoader().loadClass("org.quartz.impl.triggers.CronTriggerImpl"); + cronTriggerClass = ClassUtils.forName("org.quartz.impl.triggers.CronTriggerImpl", getClass().getClassLoader()); jobKeyMethod = JobDetail.class.getMethod("getKey"); } catch (ClassNotFoundException ex) { @@ -247,19 +276,23 @@ else if (this.startTime == null) { MutablePropertyValues pvs = new MutablePropertyValues(); pvs.add("name", this.name); pvs.add("group", this.group); - if (jobKeyMethod != null) { - pvs.add("jobKey", ReflectionUtils.invokeMethod(jobKeyMethod, this.jobDetail)); - } - else { - pvs.add("jobName", this.jobDetail.getName()); - pvs.add("jobGroup", this.jobDetail.getGroup()); + if (this.jobDetail != null) { + if (jobKeyMethod != null) { + pvs.add("jobKey", ReflectionUtils.invokeMethod(jobKeyMethod, this.jobDetail)); + } + else { + pvs.add("jobName", this.jobDetail.getName()); + pvs.add("jobGroup", this.jobDetail.getGroup()); + } } pvs.add("jobDataMap", this.jobDataMap); pvs.add("startTime", this.startTime); pvs.add("cronExpression", this.cronExpression); pvs.add("timeZone", this.timeZone); + pvs.add("calendarName", this.calendarName); pvs.add("priority", this.priority); pvs.add("misfireInstruction", this.misfireInstruction); + pvs.add("description", this.description); bw.setPropertyValues(pvs); this.cronTrigger = (CronTrigger) bw.getWrappedInstance(); } diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/JobDetailBean.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/JobDetailBean.java index 0a53406577..2105f4ae34 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/JobDetailBean.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/JobDetailBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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 @@ * Use Quartz 2.0's native {@code JobDetailImpl} class or the new Quartz 2.0 * builder API instead. Alternatively, switch to Spring's {@link JobDetailFactoryBean} * which largely is a drop-in replacement for this class and its properties and - * consistently works against Quartz 1.x as well as Quartz 2.0/2.1. + * consistently works against Quartz 1.x as well as Quartz 2.x. * * @author Juergen Hoeller * @since 18.02.2004 @@ -48,11 +48,11 @@ * @see org.springframework.beans.factory.BeanNameAware * @see org.quartz.Scheduler#DEFAULT_GROUP */ -@SuppressWarnings("serial") +@SuppressWarnings({"serial", "rawtypes"}) public class JobDetailBean extends JobDetail implements BeanNameAware, ApplicationContextAware, InitializingBean { - private Class actualJobClass; + private Class actualJobClass; private String beanName; @@ -97,7 +97,7 @@ public Class getJobClass() { * (for example Spring-managed beans) * @see SchedulerFactoryBean#setSchedulerContextAsMap */ - public void setJobDataAsMap(Map jobDataAsMap) { + public void setJobDataAsMap(Map jobDataAsMap) { getJobDataMap().putAll(jobDataAsMap); } @@ -109,9 +109,9 @@ public void setJobDataAsMap(Map jobDataAsMap) { * @see SchedulerFactoryBean#setJobListeners * @see org.quartz.JobListener#getName */ - public void setJobListenerNames(String[] names) { - for (int i = 0; i < names.length; i++) { - addJobListener(names[i]); + public void setJobListenerNames(String... names) { + for (String name : names) { + addJobListener(name); } } diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/JobDetailFactoryBean.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/JobDetailFactoryBean.java index 81d370646a..80c49a136c 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/JobDetailFactoryBean.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/JobDetailFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,6 +30,7 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.util.ClassUtils; /** * A Spring {@link FactoryBean} for creating a Quartz {@link org.quartz.JobDetail} @@ -39,7 +40,7 @@ * sensible defaults. This class uses the Spring bean name as job name, * and the Quartz default group ("DEFAULT") as job group if not specified. * - *

NOTE: This FactoryBean works against both Quartz 1.x and Quartz 2.0/2.1, + *

NOTE: This FactoryBean works against both Quartz 1.x and Quartz 2.x, * in contrast to the older {@link JobDetailBean} class. * * @author Juergen Hoeller @@ -56,12 +57,14 @@ public class JobDetailFactoryBean private String group; - private Class jobClass; + private Class jobClass; private JobDataMap jobDataMap = new JobDataMap(); private boolean durability = false; + private boolean requestsRecovery = false; + private String description; private String beanName; @@ -90,7 +93,7 @@ public void setGroup(String group) { /** * Specify the job's implementation class. */ - public void setJobClass(Class jobClass) { + public void setJobClass(Class jobClass) { this.jobClass = jobClass; } @@ -132,6 +135,14 @@ public void setDurability(boolean durability) { this.durability = durability; } + /** + * Set the recovery flag for this job, i.e. whether or not the job should + * get re-executed if a 'recovery' or 'fail-over' situation is encountered. + */ + public void setRequestsRecovery(boolean requestsRecovery) { + this.requestsRecovery = requestsRecovery; + } + /** * Set a textual description for this job. */ @@ -197,7 +208,7 @@ public void afterPropertiesSet() { Class jobDetailClass; try { - jobDetailClass = getClass().getClassLoader().loadClass("org.quartz.impl.JobDetailImpl"); + jobDetailClass = ClassUtils.forName("org.quartz.impl.JobDetailImpl", getClass().getClassLoader()); } catch (ClassNotFoundException ex) { jobDetailClass = JobDetail.class; @@ -209,6 +220,7 @@ public void afterPropertiesSet() { pvs.add("jobClass", this.jobClass); pvs.add("jobDataMap", this.jobDataMap); pvs.add("durability", this.durability); + pvs.add("requestsRecovery", this.requestsRecovery); pvs.add("description", this.description); bw.setPropertyValues(pvs); this.jobDetail = (JobDetail) bw.getWrappedInstance(); diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java index c9664b28f8..86cb3ed6d2 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,7 +18,6 @@ import java.sql.Connection; import java.sql.SQLException; - import javax.sql.DataSource; import org.quartz.SchedulerConfigException; @@ -108,14 +107,17 @@ public Connection getConnection() throws SQLException { public void shutdown() { // Do nothing - a Spring-managed DataSource has its own lifecycle. } + /* Quartz 2.2 initialize method */ + public void initialize() { + // Do nothing - a Spring-managed DataSource has its own lifecycle. + } } ); // Non-transactional DataSource is optional: fall back to default // DataSource if not explicitly specified. DataSource nonTxDataSource = SchedulerFactoryBean.getConfigTimeNonTransactionalDataSource(); - final DataSource nonTxDataSourceToUse = - (nonTxDataSource != null ? nonTxDataSource : this.dataSource); + final DataSource nonTxDataSourceToUse = (nonTxDataSource != null ? nonTxDataSource : this.dataSource); // Configure non-transactional connection settings for Quartz. setNonManagedTXDataSource(NON_TX_DATA_SOURCE_PREFIX + getInstanceName()); @@ -131,21 +133,24 @@ public Connection getConnection() throws SQLException { public void shutdown() { // Do nothing - a Spring-managed DataSource has its own lifecycle. } + /* Quartz 2.2 initialize method */ + public void initialize() { + // Do nothing - a Spring-managed DataSource has its own lifecycle. + } } ); - // No, if HSQL is the platform, we really don't want to use locks + // No, if HSQL is the platform, we really don't want to use locks... try { - String productName = JdbcUtils.extractDatabaseMetaData(dataSource, - "getDatabaseProductName").toString(); + String productName = JdbcUtils.extractDatabaseMetaData(this.dataSource, "getDatabaseProductName").toString(); productName = JdbcUtils.commonDatabaseName(productName); - if (productName != null - && productName.toLowerCase().contains("hsql")) { + if (productName != null && productName.toLowerCase().contains("hsql")) { setUseDBLocks(false); setLockHandler(new SimpleSemaphore()); } - } catch (MetaDataAccessException e) { - logWarnIfNonZero(1, "Could not detect database type. Assuming locks can be taken."); + } + catch (MetaDataAccessException ex) { + logWarnIfNonZero(1, "Could not detect database type. Assuming locks can be taken."); } super.initialize(loadHelper, signaler); diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalTaskExecutorThreadPool.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalTaskExecutorThreadPool.java index 6e5189e3b0..5106ffb6b7 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalTaskExecutorThreadPool.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalTaskExecutorThreadPool.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,7 +80,7 @@ public boolean runInThread(Runnable runnable) { } public int blockForAvailableThreads() { - // The present implementation always returns 1, making Quartz (1.6) + // The present implementation always returns 1, making Quartz // always schedule any tasks that it feels like scheduling. // This could be made smarter for specific TaskExecutors, // for example calling {@code getMaximumPoolSize() - getActiveCount()} diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/MethodInvokingJobDetailFactoryBean.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/MethodInvokingJobDetailFactoryBean.java index 80ce225457..6d910bbead 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/MethodInvokingJobDetailFactoryBean.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/MethodInvokingJobDetailFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,7 +67,7 @@ * You need to implement your own Quartz Job as a thin wrapper for each case * where you want a persistent job to delegate to a specific service method. * - *

Compatible with Quartz 1.5+ as well as Quartz 2.0/2.1, as of Spring 3.1. + *

Compatible with Quartz 1.5+ as well as Quartz 2.0-2.2, as of Spring 3.2. * * @author Juergen Hoeller * @author Alef Arendsen @@ -86,14 +86,15 @@ public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethod static { try { - jobDetailImplClass = Class.forName("org.quartz.impl.JobDetailImpl"); + jobDetailImplClass = ClassUtils.forName("org.quartz.impl.JobDetailImpl", + MethodInvokingJobDetailFactoryBean.class.getClassLoader()); } catch (ClassNotFoundException ex) { jobDetailImplClass = null; } try { - Class jobExecutionContextClass = - QuartzJobBean.class.getClassLoader().loadClass("org.quartz.JobExecutionContext"); + Class jobExecutionContextClass = ClassUtils.forName("org.quartz.JobExecutionContext", + MethodInvokingJobDetailFactoryBean.class.getClassLoader()); setResultMethod = jobExecutionContextClass.getMethod("setResult", Object.class); } catch (Exception ex) { @@ -172,7 +173,7 @@ public void setTargetBeanName(String targetBeanName) { * @see SchedulerFactoryBean#setJobListeners * @see org.quartz.JobListener#getName */ - public void setJobListenerNames(String[] names) { + public void setJobListenerNames(String... names) { this.jobListenerNames = names; } @@ -189,7 +190,7 @@ public void setBeanFactory(BeanFactory beanFactory) { } @Override - protected Class resolveClassName(String className) throws ClassNotFoundException { + protected Class resolveClassName(String className) throws ClassNotFoundException { return ClassUtils.forName(className, this.beanClassLoader); } @@ -201,7 +202,7 @@ public void afterPropertiesSet() throws ClassNotFoundException, NoSuchMethodExce String name = (this.name != null ? this.name : this.beanName); // Consider the concurrent flag to choose between stateful and stateless job. - Class jobClass = (this.concurrent ? MethodInvokingJob.class : StatefulMethodInvokingJob.class); + Class jobClass = (this.concurrent ? MethodInvokingJob.class : StatefulMethodInvokingJob.class); // Build JobDetail instance. if (jobDetailImplClass != null) { @@ -249,8 +250,8 @@ protected void postProcessJobDetail(JobDetail jobDetail) { * Overridden to support the {@link #setTargetBeanName "targetBeanName"} feature. */ @Override - public Class getTargetClass() { - Class targetClass = super.getTargetClass(); + public Class getTargetClass() { + Class targetClass = super.getTargetClass(); if (targetClass == null && this.targetBeanName != null) { Assert.state(this.beanFactory != null, "BeanFactory must be set when using 'targetBeanName'"); targetClass = this.beanFactory.getType(this.targetBeanName); diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/QuartzJobBean.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/QuartzJobBean.java index b2d61536da..1f30286adc 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/QuartzJobBean.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/QuartzJobBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import org.springframework.beans.BeanWrapper; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.PropertyAccessorFactory; +import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; /** @@ -79,8 +80,8 @@ public abstract class QuartzJobBean implements Job { static { try { - Class jobExecutionContextClass = - QuartzJobBean.class.getClassLoader().loadClass("org.quartz.JobExecutionContext"); + Class jobExecutionContextClass = + ClassUtils.forName("org.quartz.JobExecutionContext", QuartzJobBean.class.getClassLoader()); getSchedulerMethod = jobExecutionContextClass.getMethod("getScheduler"); getMergedJobDataMapMethod = jobExecutionContextClass.getMethod("getMergedJobDataMap"); } @@ -99,7 +100,7 @@ public final void execute(JobExecutionContext context) throws JobExecutionExcept try { // Reflectively adapting to differences between Quartz 1.x and Quartz 2.0... Scheduler scheduler = (Scheduler) ReflectionUtils.invokeMethod(getSchedulerMethod, context); - Map mergedJobDataMap = (Map) ReflectionUtils.invokeMethod(getMergedJobDataMapMethod, context); + Map mergedJobDataMap = (Map) ReflectionUtils.invokeMethod(getMergedJobDataMapMethod, context); BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); MutablePropertyValues pvs = new MutablePropertyValues(); diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/ResourceLoaderClassLoadHelper.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/ResourceLoaderClassLoadHelper.java index 5eba86a105..9631c51ff1 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/ResourceLoaderClassLoadHelper.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/ResourceLoaderClassLoadHelper.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. @@ -16,7 +16,6 @@ package org.springframework.scheduling.quartz; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.URL; @@ -82,32 +81,41 @@ public Class loadClass(String name, Class clazz) throws Clas public URL getResource(String name) { Resource resource = this.resourceLoader.getResource(name); - try { - return resource.getURL(); - } - catch (FileNotFoundException ex) { - return null; + if (resource.exists()) { + try { + return resource.getURL(); + } + catch (IOException ex) { + if (logger.isWarnEnabled()) { + logger.warn("Could not load " + resource); + } + return null; + } } - catch (IOException ex) { - logger.warn("Could not load " + resource); - return null; + else { + return getClassLoader().getResource(name); } } public InputStream getResourceAsStream(String name) { Resource resource = this.resourceLoader.getResource(name); - try { - return resource.getInputStream(); - } - catch (FileNotFoundException ex) { - return null; + if (resource.exists()) { + try { + return resource.getInputStream(); + } + catch (IOException ex) { + if (logger.isWarnEnabled()) { + logger.warn("Could not load " + resource); + } + return null; + } } - catch (IOException ex) { - logger.warn("Could not load " + resource); - return null; + else { + return getClassLoader().getResourceAsStream(name); } } + @Override public ClassLoader getClassLoader() { return this.resourceLoader.getClassLoader(); } 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 9c65590231..d650e6c383 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-2012 the original author or authors. + * Copyright 2002-2014 the original author 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.transaction.TransactionException; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; +import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; /** @@ -51,9 +52,10 @@ *

For concrete usage, check out the {@link SchedulerFactoryBean} and * {@link SchedulerAccessorBean} classes. * - *

Compatible with Quartz 1.5+ as well as Quartz 2.0/2.1, as of Spring 3.1. + *

Compatible with Quartz 1.5+ as well as Quartz 2.0-2.2, as of Spring 3.2. * * @author Juergen Hoeller + * @author Stephane Nicoll * @since 2.5.6 */ public abstract class SchedulerAccessor implements ResourceLoaderAware { @@ -63,9 +65,10 @@ public abstract class SchedulerAccessor implements ResourceLoaderAware { private static Class triggerKeyClass; static { + // Quartz 2.0 job/trigger key available? try { - jobKeyClass = Class.forName("org.quartz.JobKey"); - triggerKeyClass = Class.forName("org.quartz.TriggerKey"); + jobKeyClass = ClassUtils.forName("org.quartz.JobKey", SchedulerAccessor.class.getClassLoader()); + triggerKeyClass = ClassUtils.forName("org.quartz.TriggerKey", SchedulerAccessor.class.getClassLoader()); } catch (ClassNotFoundException ex) { jobKeyClass = null; @@ -128,7 +131,7 @@ public void setJobSchedulingDataLocation(String jobSchedulingDataLocation) { * to jobs defined directly on this SchedulerFactoryBean. * @see org.quartz.xml.XmlSchedulingDataProcessor */ - public void setJobSchedulingDataLocations(String[] jobSchedulingDataLocations) { + public void setJobSchedulingDataLocations(String... jobSchedulingDataLocations) { this.jobSchedulingDataLocations = jobSchedulingDataLocations; } @@ -140,13 +143,10 @@ public void setJobSchedulingDataLocations(String[] jobSchedulingDataLocations) { * in combination with the Trigger. * @see #setTriggers * @see org.quartz.JobDetail - * @see JobDetailBean - * @see JobDetailAwareTrigger - * @see org.quartz.Trigger#setJobName */ - public void setJobDetails(JobDetail[] jobDetails) { + public void setJobDetails(JobDetail... jobDetails) { // Use modifiable ArrayList here, to allow for further adding of - // JobDetail objects during autodetection of JobDetailAwareTriggers. + // JobDetail objects during autodetection of JobDetail-aware Triggers. this.jobDetails = new ArrayList(Arrays.asList(jobDetails)); } @@ -156,7 +156,6 @@ public void setJobDetails(JobDetail[] jobDetails) { * @param calendars Map with calendar names as keys as Calendar * objects as values * @see org.quartz.Calendar - * @see org.quartz.Trigger#setCalendarName */ public void setCalendars(Map calendars) { this.calendars = calendars; @@ -171,19 +170,15 @@ public void setCalendars(Map calendars) { * "jobDetails" property of this FactoryBean. * @see #setJobDetails * @see org.quartz.JobDetail - * @see JobDetailAwareTrigger - * @see CronTriggerBean - * @see SimpleTriggerBean */ - public void setTriggers(Trigger[] triggers) { + public void setTriggers(Trigger... triggers) { this.triggers = Arrays.asList(triggers); } - /** * Specify Quartz SchedulerListeners to be registered with the Scheduler. */ - public void setSchedulerListeners(SchedulerListener[] schedulerListeners) { + public void setSchedulerListeners(SchedulerListener... schedulerListeners) { this.schedulerListeners = schedulerListeners; } @@ -191,7 +186,7 @@ public void setSchedulerListeners(SchedulerListener[] schedulerListeners) { * Specify global Quartz JobListeners to be registered with the Scheduler. * Such JobListeners will apply to all Jobs in the Scheduler. */ - public void setGlobalJobListeners(JobListener[] globalJobListeners) { + public void setGlobalJobListeners(JobListener... globalJobListeners) { this.globalJobListeners = globalJobListeners; } @@ -199,11 +194,12 @@ public void setGlobalJobListeners(JobListener[] globalJobListeners) { * Specify named Quartz JobListeners to be registered with the Scheduler. * Such JobListeners will only apply to Jobs that explicitly activate * them via their name. + *

Note that non-global JobListeners are not supported on Quartz 2.x - + * manually register a Matcher against the Quartz ListenerManager instead. * @see org.quartz.JobListener#getName - * @see org.quartz.JobDetail#addJobListener * @see JobDetailBean#setJobListenerNames */ - public void setJobListeners(JobListener[] jobListeners) { + public void setJobListeners(JobListener... jobListeners) { this.jobListeners = jobListeners; } @@ -211,7 +207,7 @@ public void setJobListeners(JobListener[] jobListeners) { * Specify global Quartz TriggerListeners to be registered with the Scheduler. * Such TriggerListeners will apply to all Triggers in the Scheduler. */ - public void setGlobalTriggerListeners(TriggerListener[] globalTriggerListeners) { + public void setGlobalTriggerListeners(TriggerListener... globalTriggerListeners) { this.globalTriggerListeners = globalTriggerListeners; } @@ -219,12 +215,13 @@ public void setGlobalTriggerListeners(TriggerListener[] globalTriggerListeners) * Specify named Quartz TriggerListeners to be registered with the Scheduler. * Such TriggerListeners will only apply to Triggers that explicitly activate * them via their name. + *

Note that non-global TriggerListeners are not supported on Quartz 2.x - + * manually register a Matcher against the Quartz ListenerManager instead. * @see org.quartz.TriggerListener#getName - * @see org.quartz.Trigger#addTriggerListener * @see CronTriggerBean#setTriggerListenerNames * @see SimpleTriggerBean#setTriggerListenerNames */ - public void setTriggerListeners(TriggerListener[] triggerListeners) { + public void setTriggerListeners(TriggerListener... triggerListeners) { this.triggerListeners = triggerListeners; } @@ -251,14 +248,14 @@ protected void registerJobsAndTriggers() throws SchedulerException { if (this.transactionManager != null) { transactionStatus = this.transactionManager.getTransaction(new DefaultTransactionDefinition()); } - try { + try { if (this.jobSchedulingDataLocations != null) { ClassLoadHelper clh = new ResourceLoaderClassLoadHelper(this.resourceLoader); clh.initialize(); try { // Quartz 1.8 or higher? - Class dataProcessorClass = getClass().getClassLoader().loadClass("org.quartz.xml.XMLSchedulingDataProcessor"); + Class dataProcessorClass = ClassUtils.forName("org.quartz.xml.XMLSchedulingDataProcessor", getClass().getClassLoader()); logger.debug("Using Quartz 1.8 XMLSchedulingDataProcessor"); Object dataProcessor = dataProcessorClass.getConstructor(ClassLoadHelper.class).newInstance(clh); Method processFileAndScheduleJobs = dataProcessorClass.getMethod("processFileAndScheduleJobs", String.class, Scheduler.class); @@ -268,7 +265,7 @@ protected void registerJobsAndTriggers() throws SchedulerException { } catch (ClassNotFoundException ex) { // Quartz 1.6 - Class dataProcessorClass = getClass().getClassLoader().loadClass("org.quartz.xml.JobSchedulingDataProcessor"); + Class dataProcessorClass = ClassUtils.forName("org.quartz.xml.JobSchedulingDataProcessor", getClass().getClassLoader()); logger.debug("Using Quartz 1.6 JobSchedulingDataProcessor"); Object dataProcessor = dataProcessorClass.getConstructor(ClassLoadHelper.class, boolean.class, boolean.class).newInstance(clh, true, true); Method processFileAndScheduleJobs = dataProcessorClass.getMethod("processFileAndScheduleJobs", String.class, Scheduler.class, boolean.class); @@ -396,8 +393,9 @@ private JobDetail findJobDetail(Trigger trigger) { } else { try { - Map jobDataMap = (Map) ReflectionUtils.invokeMethod(Trigger.class.getMethod("getJobDataMap"), trigger); - return (JobDetail) jobDataMap.get(JobDetailAwareTrigger.JOB_DETAIL_KEY); + Map jobDataMap = + (Map) ReflectionUtils.invokeMethod(Trigger.class.getMethod("getJobDataMap"), trigger); + return (JobDetail) jobDataMap.remove(JobDetailAwareTrigger.JOB_DETAIL_KEY); } catch (NoSuchMethodException ex) { throw new IllegalStateException("Inconsistent Quartz API: " + ex); @@ -473,19 +471,33 @@ protected void registerListeners() throws SchedulerException { target = getScheduler(); quartz2 = false; } + Class targetClass = target.getClass(); try { if (this.schedulerListeners != null) { - Method addSchedulerListener = target.getClass().getMethod("addSchedulerListener", SchedulerListener.class); + Method addSchedulerListener = targetClass.getMethod("addSchedulerListener", SchedulerListener.class); for (SchedulerListener listener : this.schedulerListeners) { ReflectionUtils.invokeMethod(addSchedulerListener, target, listener); } } if (this.globalJobListeners != null) { - Method addJobListener = target.getClass().getMethod( - (quartz2 ? "addJobListener" : "addGlobalJobListener"), JobListener.class); + Method addJobListener; + if (quartz2) { + // addJobListener(JobListener) only introduced as late as Quartz 2.2, so we need + // to fall back to the Quartz 2.0/2.1 compatible variant with an empty matchers List + addJobListener = targetClass.getMethod("addJobListener", JobListener.class, List.class); + } + else { + addJobListener = targetClass.getMethod("addGlobalJobListener", JobListener.class); + } for (JobListener listener : this.globalJobListeners) { - ReflectionUtils.invokeMethod(addJobListener, target, listener); + if (quartz2) { + List emptyMatchers = new LinkedList(); + ReflectionUtils.invokeMethod(addJobListener, target, listener, emptyMatchers); + } + else { + ReflectionUtils.invokeMethod(addJobListener, target, listener); + } } } if (this.jobListeners != null) { @@ -498,10 +510,23 @@ protected void registerListeners() throws SchedulerException { } } if (this.globalTriggerListeners != null) { - Method addTriggerListener = target.getClass().getMethod( - (quartz2 ? "addTriggerListener" : "addGlobalTriggerListener"), TriggerListener.class); + Method addTriggerListener; + if (quartz2) { + // addTriggerListener(TriggerListener) only introduced as late as Quartz 2.2, so we need + // to fall back to the Quartz 2.0/2.1 compatible variant with an empty matchers List + addTriggerListener = targetClass.getMethod("addTriggerListener", TriggerListener.class, List.class); + } + else { + addTriggerListener = targetClass.getMethod("addGlobalTriggerListener", TriggerListener.class); + } for (TriggerListener listener : this.globalTriggerListeners) { - ReflectionUtils.invokeMethod(addTriggerListener, target, listener); + if (quartz2) { + List emptyMatchers = new LinkedList(); + ReflectionUtils.invokeMethod(addTriggerListener, target, listener, emptyMatchers); + } + else { + ReflectionUtils.invokeMethod(addTriggerListener, target, listener); + } } } if (this.triggerListeners != null) { diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessorBean.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessorBean.java index 9c69a47c00..e18dd88761 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessorBean.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessorBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ * Spring bean-style class for accessing a Quartz Scheduler, i.e. for registering jobs, * triggers and listeners on a given {@link org.quartz.Scheduler} instance. * - *

Compatible with Quartz 1.5+ as well as Quartz 2.0/2.1, as of Spring 3.1. + *

Compatible with Quartz 1.5+ as well as Quartz 2.0-2.2, as of Spring 3.2. * * @author Juergen Hoeller * @since 2.5.6 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 ed8baa41df..9396759c0c 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-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,7 @@ 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; /** @@ -74,7 +75,7 @@ * automatically apply to Scheduler operations performed within those scopes. * Alternatively, you may add transactional advice for the Scheduler itself. * - *

Compatible with Quartz 1.5+ as well as Quartz 2.0/2.1, as of Spring 3.1. + *

Compatible with Quartz 1.5+ as well as Quartz 2.0-2.2, as of Spring 3.2. * * @author Juergen Hoeller * @since 18.02.2004 @@ -84,8 +85,8 @@ * @see org.quartz.impl.StdSchedulerFactory * @see org.springframework.transaction.interceptor.TransactionProxyFactoryBean */ -public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBean, BeanNameAware, - ApplicationContextAware, InitializingBean, DisposableBean, SmartLifecycle { +public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBean, + BeanNameAware, ApplicationContextAware, InitializingBean, DisposableBean, SmartLifecycle { public static final String PROP_THREAD_COUNT = "org.quartz.threadPool.threadCount"; @@ -157,7 +158,7 @@ public static DataSource getConfigTimeNonTransactionalDataSource() { } - private Class schedulerFactoryClass = StdSchedulerFactory.class; + private Class schedulerFactoryClass = StdSchedulerFactory.class; private String schedulerName; @@ -173,7 +174,7 @@ public static DataSource getConfigTimeNonTransactionalDataSource() { private DataSource nonTransactionalDataSource; - private Map schedulerContextMap; + private Map schedulerContextMap; private ApplicationContext applicationContext; @@ -200,7 +201,7 @@ public static DataSource getConfigTimeNonTransactionalDataSource() { /** * Set the Quartz SchedulerFactory implementation to use. - *

Default is StdSchedulerFactory, reading in the standard + *

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. @@ -208,10 +209,8 @@ public static DataSource getConfigTimeNonTransactionalDataSource() { * @see #setConfigLocation * @see #setQuartzProperties */ - public void setSchedulerFactoryClass(Class schedulerFactoryClass) { - if (schedulerFactoryClass == null || !SchedulerFactory.class.isAssignableFrom(schedulerFactoryClass)) { - throw new IllegalArgumentException("schedulerFactoryClass must implement [org.quartz.SchedulerFactory]"); - } + public void setSchedulerFactoryClass(Class schedulerFactoryClass) { + Assert.isAssignable(SchedulerFactory.class, schedulerFactoryClass); this.schedulerFactoryClass = schedulerFactoryClass; } @@ -311,9 +310,9 @@ public void setNonTransactionalDataSource(DataSource nonTransactionalDataSource) * reference into the JobDataMap but rather into the SchedulerContext. * @param schedulerContextAsMap Map with String keys and any objects as * values (for example Spring-managed beans) - * @see JobDetailBean#setJobDataAsMap + * @see JobDetailFactoryBean#setJobDataAsMap */ - public void setSchedulerContextAsMap(Map schedulerContextAsMap) { + public void setSchedulerContextAsMap(Map schedulerContextAsMap) { this.schedulerContextMap = schedulerContextAsMap; } @@ -329,8 +328,8 @@ public void setSchedulerContextAsMap(Map schedulerContextAsMap) { * correspond to a "setApplicationContext" method in that scenario. *

Note that BeanFactory callback interfaces like ApplicationContextAware * are not automatically applied to Quartz Job instances, because Quartz - * itself is reponsible for the lifecycle of its Jobs. - * @see JobDetailBean#setApplicationContextJobDataKey + * itself is responsible for the lifecycle of its Jobs. + * @see JobDetailFactoryBean#setApplicationContextJobDataKey * @see org.springframework.context.ApplicationContext */ public void setApplicationContextSchedulerContextKey(String applicationContextSchedulerContextKey) { @@ -450,10 +449,8 @@ public void afterPropertiesSet() throws Exception { this.resourceLoader = this.applicationContext; } - // Create SchedulerFactory instance. - SchedulerFactory schedulerFactory = (SchedulerFactory) - BeanUtils.instantiateClass(this.schedulerFactoryClass); - + // Create SchedulerFactory instance... + SchedulerFactory schedulerFactory = BeanUtils.instantiateClass(this.schedulerFactoryClass); initSchedulerFactory(schedulerFactory); if (this.resourceLoader != null) { @@ -473,7 +470,6 @@ public void afterPropertiesSet() throws Exception { configTimeNonTransactionalDataSourceHolder.set(this.nonTransactionalDataSource); } - // Get Scheduler instance from SchedulerFactory. try { this.scheduler = createScheduler(schedulerFactory, this.schedulerName); @@ -516,9 +512,7 @@ public void afterPropertiesSet() throws Exception { * Load and/or apply Quartz properties to the given SchedulerFactory. * @param schedulerFactory the SchedulerFactory to initialize */ - private void initSchedulerFactory(SchedulerFactory schedulerFactory) - throws SchedulerException, IOException { - + private void initSchedulerFactory(SchedulerFactory schedulerFactory) throws SchedulerException, IOException { if (!(schedulerFactory instanceof StdSchedulerFactory)) { if (this.configLocation != null || this.quartzProperties != null || this.taskExecutor != null || this.dataSource != null) { @@ -703,7 +697,7 @@ public boolean isSingleton() { //--------------------------------------------------------------------- - // Implementation of Lifecycle interface + // Implementation of SmartLifecycle interface //--------------------------------------------------------------------- public void start() throws SchedulingException { diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleTriggerBean.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleTriggerBean.java index b35bb59ad9..2ab836b5c8 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleTriggerBean.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleTriggerBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,7 +46,7 @@ * Use Quartz 2.0's native {@code JobDetailImpl} class or the new Quartz 2.0 * builder API instead. Alternatively, switch to Spring's {@link SimpleTriggerFactoryBean} * which largely is a drop-in replacement for this class and its properties and - * consistently works against Quartz 1.x as well as Quartz 2.0/2.1. + * consistently works against Quartz 1.x as well as Quartz 2.x. * * @author Juergen Hoeller * @since 18.02.2004 @@ -114,9 +114,9 @@ public void setMisfireInstructionName(String constantName) { * @see SchedulerFactoryBean#setTriggerListeners * @see org.quartz.TriggerListener#getName */ - public void setTriggerListenerNames(String[] names) { - for (int i = 0; i < names.length; i++) { - addTriggerListener(names[i]); + public void setTriggerListenerNames(String... names) { + for (String name : names) { + addTriggerListener(name); } } @@ -153,6 +153,10 @@ public void setBeanName(String beanName) { } + /** + * Note that this method's declaration of a ParseException is deprecated + * and will be removed in the Spring 4.0 line. + */ public void afterPropertiesSet() throws ParseException { if (getName() == null) { setName(this.beanName); @@ -160,7 +164,7 @@ public void afterPropertiesSet() throws ParseException { if (getGroup() == null) { setGroup(Scheduler.DEFAULT_GROUP); } - if (getStartTime() == null) { + if (this.startDelay > 0 || getStartTime() == null) { setStartTime(new Date(System.currentTimeMillis() + this.startDelay)); } if (this.jobDetail != null) { diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleTriggerFactoryBean.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleTriggerFactoryBean.java index 15d1e901ab..15daf8ac5f 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleTriggerFactoryBean.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleTriggerFactoryBean.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,7 +17,6 @@ package org.springframework.scheduling.quartz; import java.lang.reflect.Method; -import java.text.ParseException; import java.util.Date; import java.util.Map; @@ -34,6 +33,7 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.core.Constants; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; /** @@ -49,7 +49,7 @@ * to automatically register a trigger for the corresponding JobDetail, * instead of registering the JobDetail separately. * - *

NOTE: This FactoryBean works against both Quartz 1.x and Quartz 2.0/2.1, + *

NOTE: This FactoryBean works against both Quartz 1.x and Quartz 2.x, * in contrast to the older {@link SimpleTriggerBean} class. * * @author Juergen Hoeller @@ -88,6 +88,8 @@ public class SimpleTriggerFactoryBean implements FactoryBean, Bea private int misfireInstruction; + private String description; + private String beanName; private SimpleTrigger simpleTrigger; @@ -141,10 +143,20 @@ public void setJobDataAsMap(Map jobDataAsMap) { this.jobDataMap.putAll(jobDataAsMap); } + /** + * Set a specific start time for the trigger. + *

Note that a dynamically computed {@link #setStartDelay} specification + * overrides a static timestamp set here. + */ + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + /** * Set the start delay in milliseconds. *

The start delay is added to the current system time (when the bean starts) * to control the start time of the trigger. + * @see #setStartTime */ public void setStartDelay(long startDelay) { Assert.isTrue(startDelay >= 0, "Start delay cannot be negative"); @@ -195,12 +207,19 @@ public void setMisfireInstructionName(String constantName) { this.misfireInstruction = constants.asNumber(constantName).intValue(); } + /** + * Associate a textual description with this trigger. + */ + public void setDescription(String description) { + this.description = description; + } + public void setBeanName(String beanName) { this.beanName = beanName; } - public void afterPropertiesSet() throws ParseException { + public void afterPropertiesSet() { if (this.name == null) { this.name = this.beanName; } @@ -210,31 +229,31 @@ public void afterPropertiesSet() throws ParseException { if (this.jobDetail != null) { this.jobDataMap.put(JobDetailAwareTrigger.JOB_DETAIL_KEY, this.jobDetail); } - if (this.startDelay > 0) { + if (this.startDelay > 0 || this.startTime == null) { this.startTime = new Date(System.currentTimeMillis() + this.startDelay); } - else if (this.startTime == null) { - this.startTime = new Date(); - } /* SimpleTriggerImpl sti = new SimpleTriggerImpl(); sti.setName(this.name); sti.setGroup(this.group); - sti.setJobKey(this.jobDetail.getKey()); + if (this.jobDetail != null) { + sti.setJobKey(this.jobDetail.getKey()); + } sti.setJobDataMap(this.jobDataMap); sti.setStartTime(this.startTime); sti.setRepeatInterval(this.repeatInterval); sti.setRepeatCount(this.repeatCount); sti.setPriority(this.priority); sti.setMisfireInstruction(this.misfireInstruction); + sti.setDescription(this.description); this.simpleTrigger = sti; */ - Class simpleTriggerClass; + Class simpleTriggerClass; Method jobKeyMethod; try { - simpleTriggerClass = getClass().getClassLoader().loadClass("org.quartz.impl.triggers.SimpleTriggerImpl"); + simpleTriggerClass = ClassUtils.forName("org.quartz.impl.triggers.SimpleTriggerImpl", getClass().getClassLoader()); jobKeyMethod = JobDetail.class.getMethod("getKey"); } catch (ClassNotFoundException ex) { @@ -248,12 +267,14 @@ else if (this.startTime == null) { MutablePropertyValues pvs = new MutablePropertyValues(); pvs.add("name", this.name); pvs.add("group", this.group); - if (jobKeyMethod != null) { - pvs.add("jobKey", ReflectionUtils.invokeMethod(jobKeyMethod, this.jobDetail)); - } - else { - pvs.add("jobName", this.jobDetail.getName()); - pvs.add("jobGroup", this.jobDetail.getGroup()); + if (this.jobDetail != null) { + if (jobKeyMethod != null) { + pvs.add("jobKey", ReflectionUtils.invokeMethod(jobKeyMethod, this.jobDetail)); + } + else { + pvs.add("jobName", this.jobDetail.getName()); + pvs.add("jobGroup", this.jobDetail.getGroup()); + } } pvs.add("jobDataMap", this.jobDataMap); pvs.add("startTime", this.startTime); @@ -261,6 +282,7 @@ else if (this.startTime == null) { pvs.add("repeatCount", this.repeatCount); pvs.add("priority", this.priority); pvs.add("misfireInstruction", this.misfireInstruction); + pvs.add("description", this.description); bw.setPropertyValues(pvs); this.simpleTrigger = (SimpleTrigger) bw.getWrappedInstance(); } @@ -277,4 +299,5 @@ public Class getObjectType() { public boolean isSingleton() { return true; } + } diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SpringBeanJobFactory.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SpringBeanJobFactory.java index a9d837e7da..ddbf7953db 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SpringBeanJobFactory.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SpringBeanJobFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,7 +37,7 @@ * as bean property values. If no matching bean property is found, the entry * is by default simply ignored. This is analogous to QuartzJobBean's behavior. * - *

Compatible with Quartz 1.5+ as well as Quartz 2.0/2.1, as of Spring 3.1. + *

Compatible with Quartz 1.5+ as well as Quartz 2.0-2.2, as of Spring 3.2. * * @author Juergen Hoeller * @since 2.0 @@ -59,7 +59,7 @@ public class SpringBeanJobFactory extends AdaptableJobFactory implements Schedul * ignored if there is no corresponding property found on the particular * job class (all other unknown properties will still trigger an exception). */ - public void setIgnoredUnknownProperties(String[] ignoredUnknownProperties) { + public void setIgnoredUnknownProperties(String... ignoredUnknownProperties) { this.ignoredUnknownProperties = ignoredUnknownProperties; } diff --git a/spring-context-support/src/main/java/org/springframework/ui/freemarker/FreeMarkerTemplateUtils.java b/spring-context-support/src/main/java/org/springframework/ui/freemarker/FreeMarkerTemplateUtils.java index b2e7a85214..751ee13dc2 100644 --- a/spring-context-support/src/main/java/org/springframework/ui/freemarker/FreeMarkerTemplateUtils.java +++ b/spring-context-support/src/main/java/org/springframework/ui/freemarker/FreeMarkerTemplateUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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 abstract class FreeMarkerTemplateUtils { */ public static String processTemplateIntoString(Template template, Object model) throws IOException, TemplateException { + StringWriter result = new StringWriter(); template.process(model, result); return result.toString(); diff --git a/spring-context-support/src/main/java/org/springframework/ui/freemarker/SpringTemplateLoader.java b/spring-context-support/src/main/java/org/springframework/ui/freemarker/SpringTemplateLoader.java index 7ed965ddb5..d2509aa87f 100644 --- a/spring-context-support/src/main/java/org/springframework/ui/freemarker/SpringTemplateLoader.java +++ b/spring-context-support/src/main/java/org/springframework/ui/freemarker/SpringTemplateLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2014 the original author 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,9 +28,9 @@ import org.springframework.core.io.ResourceLoader; /** - * FreeMarker TemplateLoader adapter that loads via a Spring ResourceLoader. - * Used by FreeMarkerConfigurationFactory for any resource loader path that - * cannot be resolved to a java.io.File. + * FreeMarker {@link TemplateLoader} adapter that loads via a Spring {@link ResourceLoader}. + * Used by {@link FreeMarkerConfigurationFactory} for any resource loader path that cannot + * be resolved to a {@link java.io.File}. * * @author Juergen Hoeller * @since 14.03.2004 @@ -63,6 +63,7 @@ public SpringTemplateLoader(ResourceLoader resourceLoader, String templateLoader } } + public Object findTemplateSource(String name) throws IOException { if (logger.isDebugEnabled()) { logger.debug("Looking for FreeMarker template with name [" + name + "]"); @@ -84,7 +85,6 @@ public Reader getReader(Object templateSource, String encoding) throws IOExcepti } } - public long getLastModified(Object templateSource) { Resource resource = (Resource) templateSource; try { diff --git a/spring-context-support/src/test/java/org/springframework/scheduling/quartz/CronTriggerFactoryBeanTests.java b/spring-context-support/src/test/java/org/springframework/scheduling/quartz/CronTriggerFactoryBeanTests.java new file mode 100644 index 0000000000..5aa0142fc7 --- /dev/null +++ b/spring-context-support/src/test/java/org/springframework/scheduling/quartz/CronTriggerFactoryBeanTests.java @@ -0,0 +1,41 @@ +/* + * 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.scheduling.quartz; + +import java.text.ParseException; + +import org.junit.Test; +import org.quartz.CronTrigger; + +import static org.junit.Assert.*; + +/** + * @author Stephane Nicoll + */ +public class CronTriggerFactoryBeanTests { + + @Test + public void createWithoutJobDetail() throws ParseException { + CronTriggerFactoryBean factory = new CronTriggerFactoryBean(); + factory.setName("myTrigger"); + factory.setCronExpression("0 15 10 ? * *"); + factory.afterPropertiesSet(); + CronTrigger trigger = factory.getObject(); + assertEquals("0 15 10 ? * *", trigger.getCronExpression()); + } + +} diff --git a/spring-context-support/src/test/java/org/springframework/scheduling/quartz/SimpleTriggerFactoryBeanTests.java b/spring-context-support/src/test/java/org/springframework/scheduling/quartz/SimpleTriggerFactoryBeanTests.java new file mode 100644 index 0000000000..7021682b23 --- /dev/null +++ b/spring-context-support/src/test/java/org/springframework/scheduling/quartz/SimpleTriggerFactoryBeanTests.java @@ -0,0 +1,43 @@ +/* + * 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.scheduling.quartz; + +import java.text.ParseException; + +import org.junit.Test; +import org.quartz.SimpleTrigger; + +import static org.junit.Assert.*; + +/** + * @author Stephane Nicoll + */ +public class SimpleTriggerFactoryBeanTests { + + @Test + public void createWithoutJobDetail() throws ParseException { + SimpleTriggerFactoryBean factory = new SimpleTriggerFactoryBean(); + factory.setName("myTrigger"); + factory.setRepeatCount(5); + factory.setRepeatInterval(1000L); + factory.afterPropertiesSet(); + SimpleTrigger trigger = factory.getObject(); + assertEquals(5, trigger.getRepeatCount()); + assertEquals(1000L, trigger.getRepeatInterval()); + } + +} diff --git a/spring-context-support/src/test/java/org/springframework/ui/jasperreports/JasperReportsUtilsTests.java b/spring-context-support/src/test/java/org/springframework/ui/jasperreports/JasperReportsUtilsTests.java deleted file mode 100644 index 7b9ebb44aa..0000000000 --- a/spring-context-support/src/test/java/org/springframework/ui/jasperreports/JasperReportsUtilsTests.java +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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.ui.jasperreports; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.ResourceBundle; - -import junit.framework.TestCase; -import net.sf.jasperreports.engine.JRDataSource; -import net.sf.jasperreports.engine.JRExporterParameter; -import net.sf.jasperreports.engine.JRParameter; -import net.sf.jasperreports.engine.JasperFillManager; -import net.sf.jasperreports.engine.JasperPrint; -import net.sf.jasperreports.engine.JasperReport; -import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource; -import net.sf.jasperreports.engine.export.JRCsvExporterParameter; -import net.sf.jasperreports.engine.export.JRExportProgressMonitor; -import net.sf.jasperreports.engine.export.JRHtmlExporter; -import net.sf.jasperreports.engine.export.JRHtmlExporterParameter; -import net.sf.jasperreports.engine.export.JRPdfExporter; -import net.sf.jasperreports.engine.export.JRPdfExporterParameter; -import net.sf.jasperreports.engine.export.JRXlsExporterParameter; -import net.sf.jasperreports.engine.util.JRLoader; -import org.apache.poi.hssf.usermodel.HSSFCell; -import org.apache.poi.hssf.usermodel.HSSFRow; -import org.apache.poi.hssf.usermodel.HSSFSheet; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; - -import org.springframework.core.io.ClassPathResource; - -/** - * @author Rob Harrop - * @author Juergen Hoeller - * @since 18.11.2004 - */ -public class JasperReportsUtilsTests extends TestCase { - - public void testRenderAsCsvWithDataSource() throws Exception { - StringWriter writer = new StringWriter(); - JasperReportsUtils.renderAsCsv(getReport(), getParameters(), getDataSource(), writer); - String output = writer.getBuffer().toString(); - assertCsvOutputCorrect(output); - } - - public void testRenderAsCsvWithCollection() throws Exception { - StringWriter writer = new StringWriter(); - JasperReportsUtils.renderAsCsv(getReport(), getParameters(), getData(), writer); - String output = writer.getBuffer().toString(); - assertCsvOutputCorrect(output); - } - - public void testRenderAsCsvWithExporterParameters() throws Exception { - StringWriter writer = new StringWriter(); - Map exporterParameters = new HashMap(); - exporterParameters.put(JRCsvExporterParameter.FIELD_DELIMITER, "~"); - JasperReportsUtils.renderAsCsv(getReport(), getParameters(), getData(), writer, exporterParameters); - String output = writer.getBuffer().toString(); - assertCsvOutputCorrect(output); - assertTrue("Delimiter is incorrect", output.contains("~")); - } - - public void testRenderAsHtmlWithDataSource() throws Exception { - StringWriter writer = new StringWriter(); - JasperReportsUtils.renderAsHtml(getReport(), getParameters(), getDataSource(), writer); - String output = writer.getBuffer().toString(); - assertHtmlOutputCorrect(output); - } - - public void testRenderAsHtmlWithCollection() throws Exception { - StringWriter writer = new StringWriter(); - JasperReportsUtils.renderAsHtml(getReport(), getParameters(), getData(), writer); - String output = writer.getBuffer().toString(); - assertHtmlOutputCorrect(output); - } - - public void testRenderAsHtmlWithExporterParameters() throws Exception { - StringWriter writer = new StringWriter(); - Map exporterParameters = new HashMap(); - String uri = "/my/uri"; - exporterParameters.put(JRHtmlExporterParameter.IMAGES_URI, uri); - JasperReportsUtils.renderAsHtml(getReport(), getParameters(), getData(), writer, exporterParameters); - String output = writer.getBuffer().toString(); - assertHtmlOutputCorrect(output); - assertTrue("URI not included", output.contains(uri)); - } - - public void testRenderAsPdfWithDataSource() throws Exception { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - JasperReportsUtils.renderAsPdf(getReport(), getParameters(), getDataSource(), os); - byte[] output = os.toByteArray(); - assertPdfOutputCorrect(output); - } - - public void testRenderAsPdfWithCollection() throws Exception { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - JasperReportsUtils.renderAsPdf(getReport(), getParameters(), getData(), os); - byte[] output = os.toByteArray(); - assertPdfOutputCorrect(output); - } - - public void testRenderAsPdfWithExporterParameters() throws Exception { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - Map exporterParameters = new HashMap(); - exporterParameters.put(JRPdfExporterParameter.PDF_VERSION, JRPdfExporterParameter.PDF_VERSION_1_6.toString()); - JasperReportsUtils.renderAsPdf(getReport(), getParameters(), getData(), os, exporterParameters); - byte[] output = os.toByteArray(); - assertPdfOutputCorrect(output); - assertTrue(new String(output).contains("PDF-1.6")); - } - - public void testRenderAsXlsWithDataSource() throws Exception { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - JasperReportsUtils.renderAsXls(getReport(), getParameters(), getDataSource(), os); - byte[] output = os.toByteArray(); - assertXlsOutputCorrect(output); - } - - public void testRenderAsXlsWithCollection() throws Exception { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - JasperReportsUtils.renderAsXls(getReport(), getParameters(), getData(), os); - byte[] output = os.toByteArray(); - assertXlsOutputCorrect(output); - } - - public void testRenderAsXlsWithExporterParameters() throws Exception { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - Map exporterParameters = new HashMap(); - - SimpleProgressMonitor monitor = new SimpleProgressMonitor(); - exporterParameters.put(JRXlsExporterParameter.PROGRESS_MONITOR, monitor); - - JasperReportsUtils.renderAsXls(getReport(), getParameters(), getData(), os, exporterParameters); - byte[] output = os.toByteArray(); - assertXlsOutputCorrect(output); - assertTrue(monitor.isInvoked()); - } - - public void testRenderWithWriter() throws Exception { - StringWriter writer = new StringWriter(); - JasperPrint print = JasperFillManager.fillReport(getReport(), getParameters(), getDataSource()); - JasperReportsUtils.render(new JRHtmlExporter(), print, writer); - String output = writer.getBuffer().toString(); - assertHtmlOutputCorrect(output); - } - - public void testRenderWithOutputStream() throws Exception { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - JasperPrint print = JasperFillManager.fillReport(getReport(), getParameters(), getDataSource()); - JasperReportsUtils.render(new JRPdfExporter(), print, os); - byte[] output = os.toByteArray(); - assertPdfOutputCorrect(output); - } - - private void assertCsvOutputCorrect(String output) { - assertTrue("Output length should be greater than 0", (output.length() > 0)); - assertTrue("Output should start with Dear Lord!", output.startsWith("Dear Lord!")); - assertTrue("Output should contain 'MeineSeite'", output.contains("MeineSeite")); - } - - private void assertHtmlOutputCorrect(String output) { - assertTrue("Output length should be greater than 0", (output.length() > 0)); - assertTrue("Output should contain ", output.contains("")); - assertTrue("Output should contain 'MeineSeite'", output.contains("MeineSeite")); - } - - private void assertPdfOutputCorrect(byte[] output) throws Exception { - assertTrue("Output length should be greater than 0", (output.length > 0)); - - String translated = new String(output, "US-ASCII"); - assertTrue("Output should start with %PDF", translated.startsWith("%PDF")); - } - - private void assertXlsOutputCorrect(byte[] output) throws Exception { - HSSFWorkbook workbook = new HSSFWorkbook(new ByteArrayInputStream(output)); - HSSFSheet sheet = workbook.getSheetAt(0); - assertNotNull("Sheet should not be null", sheet); - HSSFRow row = sheet.getRow(3); - HSSFCell cell = row.getCell((short) 1); - assertNotNull("Cell should not be null", cell); - assertEquals("Cell content should be Dear Lord!", "Dear Lord!", cell.getRichStringCellValue().getString()); - } - - private JasperReport getReport() throws Exception { - ClassPathResource resource = new ClassPathResource("DataSourceReport.jasper", getClass()); - return (JasperReport) JRLoader.loadObject(resource.getInputStream()); - } - - private Map getParameters() { - Map model = new HashMap(); - model.put("ReportTitle", "Dear Lord!"); - model.put(JRParameter.REPORT_LOCALE, Locale.GERMAN); - model.put(JRParameter.REPORT_RESOURCE_BUNDLE, - ResourceBundle.getBundle("org/springframework/ui/jasperreports/messages", Locale.GERMAN)); - return model; - } - - private JRDataSource getDataSource() { - return new JRBeanCollectionDataSource(getData()); - } - - private List getData() { - List list = new ArrayList(); - for (int x = 0; x < 10; x++) { - PersonBean bean = new PersonBean(); - bean.setId(x); - bean.setName("Rob Harrop"); - bean.setStreet("foo"); - list.add(bean); - } - return list; - } - - - private static class SimpleProgressMonitor implements JRExportProgressMonitor { - - private boolean invoked = false; - - @Override - public void afterPageExport() { - this.invoked = true; - } - - public boolean isInvoked() { - return invoked; - } - } - -} diff --git a/spring-context-support/src/test/java/org/springframework/ui/jasperreports/ProductBean.java b/spring-context-support/src/test/java/org/springframework/ui/jasperreports/ProductBean.java deleted file mode 100644 index 4d83be1e92..0000000000 --- a/spring-context-support/src/test/java/org/springframework/ui/jasperreports/ProductBean.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2002-2005 the original author 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.ui.jasperreports; - -/** - * @author Rob Harrop - */ -public class ProductBean { - - private int id; - - private String name; - - private float quantity; - - private float price; - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public float getQuantity() { - return quantity; - } - - public void setQuantity(float quantity) { - this.quantity = quantity; - } - - public float getPrice() { - return price; - } - - public void setPrice(float price) { - this.price = price; - } - -} diff --git a/spring-context/src/main/java/org/springframework/cache/Cache.java b/spring-context/src/main/java/org/springframework/cache/Cache.java index dbfa1cba84..f0710e74c4 100644 --- a/spring-context/src/main/java/org/springframework/cache/Cache.java +++ b/spring-context/src/main/java/org/springframework/cache/Cache.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ package org.springframework.cache; /** - * Interface that defines the common cache operations. + * Interface that defines common cache operations. * * Note: Due to the generic use of caching, it is recommended that * implementations allow storage of null values (for example to @@ -39,11 +39,15 @@ public interface Cache { Object getNativeCache(); /** - * Return the value to which this cache maps the specified key. Returns - * {@code null} if the cache contains no mapping for this key. - * @param key key whose associated value is to be returned. + * Return the value to which this cache maps the specified key. + *

Returns {@code null} if the cache contains no mapping for this key; + * otherwise, the cached value (which may be {@code null} itself) will + * be returned in a {@link ValueWrapper}. + * @param key the key whose associated value is to be returned * @return the value to which this cache maps the specified key, - * or {@code null} if the cache contains no mapping for this key + * contained within a {@link ValueWrapper} which may also hold + * a cached {@code null} value. A straight {@code null} being + * returned means that the cache contains no mapping for this key. */ ValueWrapper get(Object key); diff --git a/spring-context/src/main/java/org/springframework/cache/CacheManager.java b/spring-context/src/main/java/org/springframework/cache/CacheManager.java index 86228014ea..205bc9a327 100644 --- a/spring-context/src/main/java/org/springframework/cache/CacheManager.java +++ b/spring-context/src/main/java/org/springframework/cache/CacheManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2014 the original author 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,8 @@ import java.util.Collection; /** - * A manager for a set of {@link Cache}s. + * Spring's central cache manager SPI. + * Allows for retrieving named {@link Cache} regions. * * @author Costin Leau * @since 3.1 @@ -28,14 +29,14 @@ public interface CacheManager { /** * Return the cache associated with the given name. - * @param name cache identifier (must not be {@code null}) - * @return associated cache, or {@code null} if none is found + * @param name the cache identifier (must not be {@code null}) + * @return the associated cache, or {@code null} if none found */ Cache getCache(String name); /** - * Return a collection of the caches known by this cache manager. - * @return names of caches known by the cache manager. + * Return a collection of the cache names known by this manager. + * @return the names of all caches known by the cache manager */ Collection getCacheNames(); diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java b/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java index 4937eeb5e3..71f5543337 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package org.springframework.cache.annotation; import java.util.Collection; - import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; @@ -27,12 +26,11 @@ import org.springframework.context.annotation.ImportAware; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; -import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; /** - * Abstract base {@code @Configuration} class providing common structure for enabling - * Spring's annotation-driven cache management capability. + * Abstract base {@code @Configuration} class providing common structure + * for enabling Spring's annotation-driven cache management capability. * * @author Chris Beams * @since 3.1 @@ -42,22 +40,28 @@ public abstract class AbstractCachingConfiguration implements ImportAware { protected AnnotationAttributes enableCaching; + protected CacheManager cacheManager; + protected KeyGenerator keyGenerator; - @Autowired(required=false) + @Autowired(required = false) private Collection cacheManagerBeans; - @Autowired(required=false) + + @Autowired(required = false) private Collection cachingConfigurers; + public void setImportMetadata(AnnotationMetadata importMetadata) { this.enableCaching = AnnotationAttributes.fromMap( importMetadata.getAnnotationAttributes(EnableCaching.class.getName(), false)); - Assert.notNull(this.enableCaching, - "@EnableCaching is not present on importing class " + - importMetadata.getClassName()); + if (this.enableCaching == null) { + throw new IllegalArgumentException( + "@EnableCaching is not present on importing class " + importMetadata.getClassName()); + } } + /** * Determine which {@code CacheManager} bean to use. Prefer the result of * {@link CachingConfigurer#cacheManager()} over any by-type matching. If none, fall @@ -68,20 +72,20 @@ public void setImportMetadata(AnnotationMetadata importMetadata) { */ @PostConstruct protected void reconcileCacheManager() { - if (!CollectionUtils.isEmpty(cachingConfigurers)) { - int nConfigurers = cachingConfigurers.size(); + if (!CollectionUtils.isEmpty(this.cachingConfigurers)) { + int nConfigurers = this.cachingConfigurers.size(); if (nConfigurers > 1) { throw new IllegalStateException(nConfigurers + " implementations of " + "CachingConfigurer were found when only 1 was expected. " + "Refactor the configuration such that CachingConfigurer is " + "implemented only once or not at all."); } - CachingConfigurer cachingConfigurer = cachingConfigurers.iterator().next(); + CachingConfigurer cachingConfigurer = this.cachingConfigurers.iterator().next(); this.cacheManager = cachingConfigurer.cacheManager(); this.keyGenerator = cachingConfigurer.keyGenerator(); } - else if (!CollectionUtils.isEmpty(cacheManagerBeans)) { - int nManagers = cacheManagerBeans.size(); + else if (!CollectionUtils.isEmpty(this.cacheManagerBeans)) { + int nManagers = this.cacheManagerBeans.size(); if (nManagers > 1) { throw new IllegalStateException(nManagers + " beans of type CacheManager " + "were found when only 1 was expected. Remove all but one of the " + @@ -89,8 +93,7 @@ else if (!CollectionUtils.isEmpty(cacheManagerBeans)) { "to make explicit which CacheManager should be used for " + "annotation-driven cache management."); } - CacheManager cacheManager = cacheManagerBeans.iterator().next(); - this.cacheManager = cacheManager; + this.cacheManager = cacheManager = this.cacheManagerBeans.iterator().next(); // keyGenerator remains null; will fall back to default within CacheInterceptor } else { @@ -99,4 +102,5 @@ else if (!CollectionUtils.isEmpty(cacheManagerBeans)) { "from your configuration."); } } + } diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java b/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java index 771487a56f..ae3cbdf7fc 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/EnableCaching.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -151,13 +151,12 @@ * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed * to standard Java interface-based proxies. The default is {@code false}. * Applicable only if {@link #mode()} is set to {@link AdviceMode#PROXY}. - * *

Note that setting this attribute to {@code true} will affect all - * Spring-managed beans requiring proxying, not just those marked with - * {@code @Cacheable}. For example, other beans marked with Spring's - * {@code @Transactional} annotation will be upgraded to subclass proxying at the same - * time. This approach has no negative impact in practice unless one is explicitly - * expecting one type of proxy vs another, e.g. in tests. + * Spring-managed beans requiring proxying, not just those marked with {@code @Cacheable}. + * For example, other beans marked with Spring's {@code @Transactional} annotation will + * be upgraded to subclass proxying at the same time. This approach has no negative + * impact in practice unless one is explicitly expecting one type of proxy vs another, + * e.g. in tests. */ boolean proxyTargetClass() default false; @@ -174,4 +173,5 @@ * The default is {@link Ordered#LOWEST_PRECEDENCE}. */ int order() default Ordered.LOWEST_PRECEDENCE; + } diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java b/spring-context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java index 91e5366f5d..21604ddb73 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,11 +37,11 @@ @Configuration public class ProxyCachingConfiguration extends AbstractCachingConfiguration { - @Bean(name=AnnotationConfigUtils.CACHE_ADVISOR_BEAN_NAME) + @Bean(name = AnnotationConfigUtils.CACHE_ADVISOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() { BeanFactoryCacheOperationSourceAdvisor advisor = - new BeanFactoryCacheOperationSourceAdvisor(); + new BeanFactoryCacheOperationSourceAdvisor(); advisor.setCacheOperationSource(cacheOperationSource()); advisor.setAdvice(cacheInterceptor()); advisor.setOrder(this.enableCaching.getNumber("order")); diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java b/spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java index d8c7575ce7..34144d9060 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.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. @@ -41,6 +41,7 @@ @SuppressWarnings("serial") public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable { + @Override public Collection parseCacheAnnotations(AnnotatedElement ae) { Collection ops = null; @@ -54,24 +55,28 @@ public Collection parseCacheAnnotations(AnnotatedElement ae) { Collection evicts = getAnnotations(ae, CacheEvict.class); if (evicts != null) { ops = lazyInit(ops); - for (CacheEvict e : evicts) { - ops.add(parseEvictAnnotation(ae, e)); + for (CacheEvict evict : evicts) { + ops.add(parseEvictAnnotation(ae, evict)); } } - Collection updates = getAnnotations(ae, CachePut.class); - if (updates != null) { + Collection puts = getAnnotations(ae, CachePut.class); + if (puts != null) { ops = lazyInit(ops); - for (CachePut p : updates) { - ops.add(parseUpdateAnnotation(ae, p)); + for (CachePut put : puts) { + ops.add(parsePutAnnotation(ae, put)); } } - Collection caching = getAnnotations(ae, Caching.class); - if (caching != null) { + Collection cachings = getAnnotations(ae, Caching.class); + if (cachings != null) { ops = lazyInit(ops); - for (Caching c : caching) { - ops.addAll(parseCachingAnnotation(ae, c)); + for (Caching caching : cachings) { + Collection cachingOps = parseCachingAnnotation(ae, caching); + if (cachingOps != null) { + ops.addAll(cachingOps); + } } } + return ops; } @@ -80,34 +85,34 @@ private Collection lazyInit(Collection parseCachingAnnotation(AnnotatedElement ae, Caching caching) { @@ -131,7 +136,7 @@ Collection parseCachingAnnotation(AnnotatedElement ae, Caching c if (!ObjectUtils.isEmpty(updates)) { ops = lazyInit(ops); for (CachePut update : updates) { - ops.add(parseUpdateAnnotation(ae, update)); + ops.add(parsePutAnnotation(ae, update)); } } diff --git a/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java b/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java index 54b9b6ef14..5ba4e2c2aa 100644 --- a/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java +++ b/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 the original author 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,16 +16,17 @@ package org.springframework.cache.concurrent; -import org.springframework.cache.Cache; -import org.springframework.cache.support.SimpleValueWrapper; - import java.io.Serializable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import org.springframework.cache.Cache; +import org.springframework.cache.support.SimpleValueWrapper; +import org.springframework.util.Assert; + /** - * Simple {@link Cache} implementation based on the core JDK - * {@code java.util.concurrent} package. + * Simple {@link org.springframework.cache.Cache} implementation based on the + * core JDK {@code java.util.concurrent} package. * *

Useful for testing or simple caching scenarios, typically in combination * with {@link org.springframework.cache.support.SimpleCacheManager} or @@ -62,7 +63,8 @@ public ConcurrentMapCache(String name) { /** * Create a new ConcurrentMapCache with the specified name. * @param name the name of the cache - * @param allowNullValues whether to accept and convert null values for this cache + * @param allowNullValues whether to accept and convert {@code null} + * values for this cache */ public ConcurrentMapCache(String name, boolean allowNullValues) { this(name, new ConcurrentHashMap(256), allowNullValues); @@ -70,13 +72,15 @@ public ConcurrentMapCache(String name, boolean allowNullValues) { /** * Create a new ConcurrentMapCache with the specified name and the - * given internal ConcurrentMap to use. + * given internal {@link ConcurrentMap} to use. * @param name the name of the cache * @param store the ConcurrentMap to use as an internal store * @param allowNullValues whether to allow {@code null} values * (adapting them to an internal null holder value) */ public ConcurrentMapCache(String name, ConcurrentMap store, boolean allowNullValues) { + Assert.notNull(name, "Name must not be null"); + Assert.notNull(store, "Store must not be null"); this.name = name; this.store = store; this.allowNullValues = allowNullValues; @@ -142,6 +146,10 @@ protected Object toStoreValue(Object userValue) { @SuppressWarnings("serial") private static class NullHolder implements Serializable { + + private Object readResolve() { + return NULL_HOLDER; + } } } diff --git a/spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java b/spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java index 5cae8fdff4..75f3887f43 100644 --- a/spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java +++ b/spring-context/src/main/java/org/springframework/cache/config/AnnotationDrivenCacheBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2014 the original author 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.cache.config; -import static org.springframework.context.annotation.AnnotationConfigUtils.*; +import org.w3c.dom.Element; import org.springframework.aop.config.AopNamespaceUtils; import org.springframework.beans.factory.config.BeanDefinition; @@ -26,10 +26,9 @@ import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.cache.annotation.AnnotationCacheOperationSource; import org.springframework.cache.interceptor.BeanFactoryCacheOperationSourceAdvisor; import org.springframework.cache.interceptor.CacheInterceptor; -import org.w3c.dom.Element; +import org.springframework.context.annotation.AnnotationConfigUtils; /** * {@link org.springframework.beans.factory.xml.BeanDefinitionParser} @@ -74,24 +73,21 @@ private static void parseCacheManagerProperty(Element element, BeanDefinition de /** * Registers a - *

+	 * 
 	 * 
 	 *   
 	 *   
 	 * 
-	 *
 	 * 
- * @param element - * @param parserContext */ private void registerCacheAspect(Element element, ParserContext parserContext) { - if (!parserContext.getRegistry().containsBeanDefinition(CACHE_ASPECT_BEAN_NAME)) { + if (!parserContext.getRegistry().containsBeanDefinition(AnnotationConfigUtils.CACHE_ASPECT_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(); - def.setBeanClassName(CACHE_ASPECT_CLASS_NAME); + def.setBeanClassName(AnnotationConfigUtils.CACHE_ASPECT_CLASS_NAME); def.setFactoryMethodName("aspectOf"); parseCacheManagerProperty(element, def); CacheNamespaceHandler.parseKeyGenerator(element, def); - parserContext.registerBeanComponent(new BeanComponentDefinition(def, CACHE_ASPECT_BEAN_NAME)); + parserContext.registerBeanComponent(new BeanComponentDefinition(def, AnnotationConfigUtils.CACHE_ASPECT_BEAN_NAME)); } } @@ -104,11 +100,11 @@ private static class AopAutoProxyConfigurer { public static void configureAutoProxyCreator(Element element, ParserContext parserContext) { AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element); - if (!parserContext.getRegistry().containsBeanDefinition(CACHE_ADVISOR_BEAN_NAME)) { + if (!parserContext.getRegistry().containsBeanDefinition(AnnotationConfigUtils.CACHE_ADVISOR_BEAN_NAME)) { Object eleSource = parserContext.extractSource(element); // Create the CacheOperationSource definition. - RootBeanDefinition sourceDef = new RootBeanDefinition(AnnotationCacheOperationSource.class); + RootBeanDefinition sourceDef = new RootBeanDefinition("org.springframework.cache.annotation.AnnotationCacheOperationSource"); sourceDef.setSource(eleSource); sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef); @@ -131,15 +127,16 @@ public static void configureAutoProxyCreator(Element element, ParserContext pars if (element.hasAttribute("order")) { advisorDef.getPropertyValues().add("order", element.getAttribute("order")); } - parserContext.getRegistry().registerBeanDefinition(CACHE_ADVISOR_BEAN_NAME, advisorDef); + parserContext.getRegistry().registerBeanDefinition(AnnotationConfigUtils.CACHE_ADVISOR_BEAN_NAME, advisorDef); CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource); compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName)); compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName)); - compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, CACHE_ADVISOR_BEAN_NAME)); + compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, AnnotationConfigUtils.CACHE_ADVISOR_BEAN_NAME)); parserContext.registerComponent(compositeDef); } } } + } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java index 978dbd8c36..d71a16efde 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -212,13 +212,14 @@ public boolean equals(Object other) { return false; } DefaultCacheKey otherKey = (DefaultCacheKey) other; - return (this.method.equals(otherKey.method) && ObjectUtils.nullSafeEquals(this.targetClass, - otherKey.targetClass)); + return (this.method.equals(otherKey.method) && + ObjectUtils.nullSafeEquals(this.targetClass, otherKey.targetClass)); } @Override public int hashCode() { - return this.method.hashCode() * 29 + (this.targetClass != null ? this.targetClass.hashCode() : 0); + return this.method.hashCode() + (this.targetClass != null ? this.targetClass.hashCode() * 29 : 0); } } + } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java index 8e0b36889d..bf6b8439e7 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author 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,6 +25,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.aop.framework.AopProxyUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.cache.Cache; @@ -61,23 +62,25 @@ */ public abstract class CacheAspectSupport implements InitializingBean { - public interface Invoker { - Object invoke(); - } + private static final String CACHEABLE = "cacheable"; + + private static final String UPDATE = "cacheupdate"; + + private static final String EVICT = "cacheevict"; + protected final Log logger = LogFactory.getLog(getClass()); + private final ExpressionEvaluator evaluator = new ExpressionEvaluator(); + private CacheManager cacheManager; private CacheOperationSource cacheOperationSource; - private final ExpressionEvaluator evaluator = new ExpressionEvaluator(); - private KeyGenerator keyGenerator = new DefaultKeyGenerator(); private boolean initialized = false; - private static final String CACHEABLE = "cacheable", UPDATE = "cacheupdate", EVICT = "cacheevict"; /** * Set the CacheManager that this cache aspect should delegate to. @@ -100,11 +103,9 @@ public CacheManager getCacheManager() { * @param cacheOperationSources must not be {@code null} */ public void setCacheOperationSources(CacheOperationSource... cacheOperationSources) { - Assert.notEmpty(cacheOperationSources); - this.cacheOperationSource = - (cacheOperationSources.length > 1 ? - new CompositeCacheOperationSource(cacheOperationSources) : - cacheOperationSources[0]); + Assert.notEmpty(cacheOperationSources, "At least 1 CacheOperationSource needs to be specified"); + this.cacheOperationSource = (cacheOperationSources.length > 1 ? + new CompositeCacheOperationSource(cacheOperationSources) : cacheOperationSources[0]); } /** @@ -131,16 +132,17 @@ public KeyGenerator getKeyGenerator() { public void afterPropertiesSet() { if (this.cacheManager == null) { - throw new IllegalStateException("'cacheManager' is required"); + throw new IllegalStateException("Property 'cacheManager' is required"); } if (this.cacheOperationSource == null) { - throw new IllegalStateException("The 'cacheOperationSources' property is required: " - + "If there are no cacheable methods, then don't use a cache aspect."); + throw new IllegalStateException("Property 'cacheOperationSources' is required: " + + "If there are no cacheable methods, then don't use a cache aspect."); } this.initialized = true; } + /** * Convenience method to return a String representation of this Method * for use in logging. Can be overridden in subclasses to provide a @@ -161,7 +163,7 @@ protected Collection getCaches(CacheOperation operation) { for (String cacheName : cacheNames) { Cache cache = this.cacheManager.getCache(cacheName); if (cache == null) { - throw new IllegalArgumentException("Cannot find cache named [" + cacheName + "] for " + operation); + throw new IllegalArgumentException("Cannot find cache named '" + cacheName + "' for " + operation); } caches.add(cache); } @@ -186,39 +188,31 @@ protected Object execute(Invoker invoker, Object target, Method method, Object[] if (targetClass == null && target != null) { targetClass = target.getClass(); } - final Collection cacheOp = getCacheOperationSource().getCacheOperations(method, targetClass); + Collection cacheOp = getCacheOperationSource().getCacheOperations(method, targetClass); // analyze caching information if (!CollectionUtils.isEmpty(cacheOp)) { Map> ops = createOperationContext(cacheOp, method, args, target, targetClass); - // start with evictions inspectBeforeCacheEvicts(ops.get(EVICT)); - // follow up with cacheable CacheStatus status = inspectCacheables(ops.get(CACHEABLE)); - - Object retVal = null; + Object retVal; Map updates = inspectCacheUpdates(ops.get(UPDATE)); - if (status != null) { if (status.updateRequired) { - updates.putAll(status.cUpdates); + updates.putAll(status.cacheUpdates); } // return cached object else { return status.retVal; } } - retVal = invoker.invoke(); - inspectAfterCacheEvicts(ops.get(EVICT), retVal); - if (!updates.isEmpty()) { update(updates, retVal); } - return retVal; } @@ -229,27 +223,20 @@ private void inspectBeforeCacheEvicts(Collection eviction inspectCacheEvicts(evictions, true, ExpressionEvaluator.NO_RESULT); } - private void inspectAfterCacheEvicts(Collection evictions, - Object result) { + private void inspectAfterCacheEvicts(Collection evictions, Object result) { inspectCacheEvicts(evictions, false, result); } - private void inspectCacheEvicts(Collection evictions, - boolean beforeInvocation, Object result) { - + private void inspectCacheEvicts(Collection evictions, boolean beforeInvocation, Object result) { if (!evictions.isEmpty()) { - boolean log = logger.isTraceEnabled(); - for (CacheOperationContext context : evictions) { CacheEvictOperation evictOp = (CacheEvictOperation) context.operation; - if (beforeInvocation == evictOp.isBeforeInvocation()) { if (context.isConditionPassing(result)) { // for each cache // lazy key initialization Object key = null; - for (Cache cache : context.getCaches()) { // cache-wide flush if (evictOp.isCacheWide()) { @@ -257,7 +244,8 @@ private void inspectCacheEvicts(Collection evictions, if (log) { logger.trace("Invalidating entire cache for operation " + evictOp + " on method " + context.method); } - } else { + } + else { // check key if (key == null) { key = context.generateKey(); @@ -268,7 +256,8 @@ private void inspectCacheEvicts(Collection evictions, cache.evict(key); } } - } else { + } + else { if (log) { logger.trace("Cache condition failed on method " + context.method + " for operation " + context.operation); } @@ -279,49 +268,37 @@ private void inspectCacheEvicts(Collection evictions, } private CacheStatus inspectCacheables(Collection cacheables) { - Map cUpdates = new LinkedHashMap(cacheables.size()); - - boolean updateRequired = false; + Map cacheUpdates = new LinkedHashMap(cacheables.size()); + boolean cacheHit = false; Object retVal = null; if (!cacheables.isEmpty()) { boolean log = logger.isTraceEnabled(); boolean atLeastOnePassed = false; - for (CacheOperationContext context : cacheables) { if (context.isConditionPassing()) { atLeastOnePassed = true; Object key = context.generateKey(); - if (log) { logger.trace("Computed cache key " + key + " for operation " + context.operation); } if (key == null) { - throw new IllegalArgumentException( - "Null key returned for cache operation (maybe you are using named params on classes without debug info?) " - + context.operation); + throw new IllegalArgumentException("Null key returned for cache operation (maybe you " + + "are using named params on classes without debug info?) " + context.operation); } - // add op/key (in case an update is discovered later on) - cUpdates.put(context, key); - - boolean localCacheHit = false; - + cacheUpdates.put(context, key); // check whether the cache needs to be inspected or not (the method will be invoked anyway) - if (!updateRequired) { + if (!cacheHit) { for (Cache cache : context.getCaches()) { Cache.ValueWrapper wrapper = cache.get(key); if (wrapper != null) { retVal = wrapper.get(); - localCacheHit = true; + cacheHit = true; break; } } } - - if (!localCacheHit) { - updateRequired = true; - } } else { if (log) { @@ -330,51 +307,31 @@ private CacheStatus inspectCacheables(Collection cacheabl } } - // return a status only if at least on cacheable matched + // return a status only if at least one cacheable matched if (atLeastOnePassed) { - return new CacheStatus(cUpdates, updateRequired, retVal); + return new CacheStatus(cacheUpdates, !cacheHit, retVal); } } return null; } - private static class CacheStatus { - // caches/key - final Map cUpdates; - final boolean updateRequired; - final Object retVal; - - CacheStatus(Map cUpdates, boolean updateRequired, Object retVal) { - this.cUpdates = cUpdates; - this.updateRequired = updateRequired; - this.retVal = retVal; - } - } - private Map inspectCacheUpdates(Collection updates) { - - Map cUpdates = new LinkedHashMap(updates.size()); - + Map cacheUpdates = new LinkedHashMap(updates.size()); if (!updates.isEmpty()) { boolean log = logger.isTraceEnabled(); - for (CacheOperationContext context : updates) { if (context.isConditionPassing()) { - Object key = context.generateKey(); - if (log) { logger.trace("Computed cache key " + key + " for operation " + context.operation); } if (key == null) { - throw new IllegalArgumentException( - "Null key returned for cache operation (maybe you are using named params on classes without debug info?) " - + context.operation); + throw new IllegalArgumentException("Null key returned for cache operation (maybe you " + + "are using named params on classes without debug info?) " + context.operation); } - // add op/key (in case an update is discovered later on) - cUpdates.put(context, key); + cacheUpdates.put(context, key); } else { if (log) { @@ -383,14 +340,13 @@ private Map inspectCacheUpdates(Collection updates, Object retVal) { for (Map.Entry entry : updates.entrySet()) { CacheOperationContext operationContext = entry.getKey(); - if(operationContext.canPutToCache(retVal)) { + if (operationContext.canPutToCache(retVal)) { for (Cache cache : operationContext.getCaches()) { cache.put(entry.getValue(), retVal); } @@ -398,37 +354,40 @@ private void update(Map updates, Object retVal) { } } - private Map> createOperationContext(Collection cacheOp, - Method method, Object[] args, Object target, Class targetClass) { - Map> map = new LinkedHashMap>(3); + private Map> createOperationContext( + Collection cacheOperations, Method method, Object[] args, Object target, Class targetClass) { + Map> result = new LinkedHashMap>(3); Collection cacheables = new ArrayList(); Collection evicts = new ArrayList(); Collection updates = new ArrayList(); - for (CacheOperation cacheOperation : cacheOp) { + for (CacheOperation cacheOperation : cacheOperations) { CacheOperationContext opContext = getOperationContext(cacheOperation, method, args, target, targetClass); - if (cacheOperation instanceof CacheableOperation) { cacheables.add(opContext); } - if (cacheOperation instanceof CacheEvictOperation) { evicts.add(opContext); } - if (cacheOperation instanceof CachePutOperation) { updates.add(opContext); } } - map.put(CACHEABLE, cacheables); - map.put(EVICT, evicts); - map.put(UPDATE, updates); + result.put(CACHEABLE, cacheables); + result.put(EVICT, evicts); + result.put(UPDATE, updates); + return result; + } + - return map; + public interface Invoker { + + Object invoke(); } + protected class CacheOperationContext { private final CacheOperation operation; @@ -459,8 +418,7 @@ protected boolean isConditionPassing() { protected boolean isConditionPassing(Object result) { if (StringUtils.hasText(this.operation.getCondition())) { EvaluationContext evaluationContext = createEvaluationContext(result); - return evaluator.condition(this.operation.getCondition(), this.method, - evaluationContext); + return evaluator.condition(this.operation.getCondition(), this.method, evaluationContext); } return true; } @@ -473,7 +431,7 @@ protected boolean canPutToCache(Object value) { else if (this.operation instanceof CachePutOperation) { unless = ((CachePutOperation) this.operation).getUnless(); } - if(StringUtils.hasText(unless)) { + if (StringUtils.hasText(unless)) { EvaluationContext evaluationContext = createEvaluationContext(value); return !evaluator.unless(unless, this.method, evaluationContext); } @@ -493,12 +451,29 @@ protected Object generateKey() { } private EvaluationContext createEvaluationContext(Object result) { - return evaluator.createEvaluationContext(this.caches, this.method, this.args, - this.target, this.targetClass, result); + return evaluator.createEvaluationContext(this.caches, this.method, this.args, this.target, this.targetClass, result); } protected Collection getCaches() { return this.caches; } } + + + private static class CacheStatus { + + // caches/key + final Map cacheUpdates; + + final boolean updateRequired; + + final Object retVal; + + CacheStatus(Map cacheUpdates, boolean updateRequired, Object retVal) { + this.cacheUpdates = cacheUpdates; + this.updateRequired = updateRequired; + this.retVal = retVal; + } + } + } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java index 46d84590e8..713258dcef 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author 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,32 +23,29 @@ import org.springframework.util.Assert; /** - * Base class implementing {@link CacheOperation}. + * Base class for cache operations. * * @author Costin Leau + * @since 3.1 */ public abstract class CacheOperation { - private Set cacheNames = Collections.emptySet(); - private String condition = ""; - private String key = ""; private String name = ""; + private Set cacheNames = Collections.emptySet(); - public Set getCacheNames() { - return cacheNames; - } + private String key = ""; - public String getCondition() { - return condition; - } + private String condition = ""; - public String getKey() { - return key; + + public void setName(String name) { + Assert.hasText(name); + this.name = name; } public String getName() { - return name; + return this.name; } public void setCacheName(String cacheName) { @@ -56,17 +53,16 @@ public void setCacheName(String cacheName) { this.cacheNames = Collections.singleton(cacheName); } - public void setCacheNames(String[] cacheNames) { - Assert.notEmpty(cacheNames); + public void setCacheNames(String... cacheNames) { this.cacheNames = new LinkedHashSet(cacheNames.length); - for (String string : cacheNames) { - this.cacheNames.add(string); + for (String cacheName : cacheNames) { + Assert.hasText(cacheName, "Cache name must be non-null if specified"); + this.cacheNames.add(cacheName); } } - public void setCondition(String condition) { - Assert.notNull(condition); - this.condition = condition; + public Set getCacheNames() { + return this.cacheNames; } public void setKey(String key) { @@ -74,11 +70,20 @@ public void setKey(String key) { this.key = key; } - public void setName(String name) { - Assert.hasText(name); - this.name = name; + public String getKey() { + return this.key; + } + + public void setCondition(String condition) { + Assert.notNull(condition); + this.condition = condition; + } + + public String getCondition() { + return this.condition; } + /** * This implementation compares the {@code toString()} results. * @see #toString() @@ -113,17 +118,12 @@ public String toString() { *

Available to subclasses, for inclusion in their {@code toString()} result. */ protected StringBuilder getOperationDescription() { - StringBuilder result = new StringBuilder(); - result.append(getClass().getSimpleName()); - result.append("["); - result.append(this.name); - result.append("] caches="); - result.append(this.cacheNames); - result.append(" | key='"); - result.append(this.key); - result.append("' | condition='"); - result.append(this.condition); - result.append("'"); + StringBuilder result = new StringBuilder(getClass().getSimpleName()); + result.append("[").append(this.name); + result.append("] caches=").append(this.cacheNames); + result.append(" | key='").append(this.key); + result.append("' | condition='").append(this.condition).append("'"); return result; } + } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/DefaultKeyGenerator.java b/spring-context/src/main/java/org/springframework/cache/interceptor/DefaultKeyGenerator.java index cb0dcc95d8..ed6c0e96cb 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/DefaultKeyGenerator.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/DefaultKeyGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2014 the original author 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,8 +17,7 @@ package org.springframework.cache.interceptor; import java.lang.reflect.Method; - -import org.springframework.cache.interceptor.KeyGenerator; +import java.util.Arrays; /** * Default key generator. Returns {@value #NO_PARAM_KEY} if no @@ -29,25 +28,29 @@ * * @author Costin Leau * @author Chris Beams + * @author Juergen Hoeller * @since 3.1 */ public class DefaultKeyGenerator implements KeyGenerator { public static final int NO_PARAM_KEY = 0; + public static final int NULL_PARAM_KEY = 53; public Object generate(Object target, Method method, Object... params) { - if (params.length == 1) { - return (params[0] == null ? NULL_PARAM_KEY : params[0]); - } if (params.length == 0) { return NO_PARAM_KEY; } - int hashCode = 17; - for (Object object : params) { - hashCode = 31 * hashCode + (object == null ? NULL_PARAM_KEY : object.hashCode()); + if (params.length == 1) { + Object param = params[0]; + if (param == null) { + return NULL_PARAM_KEY; + } + if (!param.getClass().isArray()) { + return param; + } } - return Integer.valueOf(hashCode); + return Arrays.deepHashCode(params); } } diff --git a/spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java b/spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java index 29a5a98973..a6b2064dc4 100644 --- a/spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java +++ b/spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author 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,12 +45,11 @@ public abstract class AbstractCacheManager implements CacheManager, Initializing public void afterPropertiesSet() { Collection caches = loadCaches(); - // preserve the initial order of the cache names + // Preserve the initial order of the cache names this.cacheMap.clear(); this.cacheNames.clear(); for (Cache cache : caches) { - this.cacheMap.put(cache.getName(), decorateCache(cache)); - this.cacheNames.add(cache.getName()); + addCache(cache); } } @@ -80,8 +79,9 @@ public Collection getCacheNames() { /** - * Load the caches for this cache manager. Occurs at startup. - * The returned collection must not be null. + * Load the initial caches for this cache manager. + *

Called by {@link #afterPropertiesSet()} on startup. + * The returned collection may be empty but must not be {@code null}. */ protected abstract Collection loadCaches(); diff --git a/spring-context/src/main/java/org/springframework/cache/support/CompositeCacheManager.java b/spring-context/src/main/java/org/springframework/cache/support/CompositeCacheManager.java index ab5b96d867..00caf7f0ea 100644 --- a/spring-context/src/main/java/org/springframework/cache/support/CompositeCacheManager.java +++ b/spring-context/src/main/java/org/springframework/cache/support/CompositeCacheManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2014 the original author 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,42 +17,73 @@ package org.springframework.cache.support; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import org.springframework.beans.factory.InitializingBean; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; -import org.springframework.util.Assert; /** - * Composite {@link CacheManager} implementation that iterates - * over a given collection of {@link CacheManager} instances. + * Composite {@link CacheManager} implementation that iterates over + * a given collection of delegate {@link CacheManager} instances. * - * Allows {@link NoOpCacheManager} to be automatically added to the list for handling - * the cache declarations without a backing store. + *

Allows {@link NoOpCacheManager} to be automatically added to the end of + * the list for handling cache declarations without a backing store. Otherwise, + * any custom {@link CacheManager} may play that role of the last delegate as + * well, lazily creating cache regions for any requested name. + * + *

Note: Regular CacheManagers that this composite manager delegates to need + * to return {@code null} from {@link #getCache(String)} if they are unaware of + * the specified cache name, allowing for iteration to the next delegate in line. + * However, most {@link CacheManager} implementations fall back to lazy creation + * of named caches once requested; check out the specific configuration details + * for a 'static' mode with fixed cache names, if available. * * @author Costin Leau * @author Juergen Hoeller * @since 3.1 + * @see #setFallbackToNoOpCache + * @see org.springframework.cache.concurrent.ConcurrentMapCacheManager#setCacheNames */ -public class CompositeCacheManager implements InitializingBean, CacheManager { +public class CompositeCacheManager implements CacheManager, InitializingBean { - private List cacheManagers; + private final List cacheManagers = new ArrayList(); private boolean fallbackToNoOpCache = false; + /** + * Construct an empty CompositeCacheManager, with delegate CacheManagers to + * be added via the {@link #setCacheManagers "cacheManagers"} property. + */ + public CompositeCacheManager() { + } + + /** + * Construct a CompositeCacheManager from the given delegate CacheManagers. + * @param cacheManagers the CacheManagers to delegate to + */ + public CompositeCacheManager(CacheManager... cacheManagers) { + setCacheManagers(Arrays.asList(cacheManagers)); + } + + + /** + * Specify the CacheManagers to delegate to. + */ public void setCacheManagers(Collection cacheManagers) { - Assert.notEmpty(cacheManagers, "cacheManagers Collection must not be empty"); - this.cacheManagers = new ArrayList(); + this.cacheManagers.clear(); // just here to preserve compatibility with previous behavior this.cacheManagers.addAll(cacheManagers); } /** - * Indicate whether a {@link NoOpCacheManager} should be added at the end of the manager lists. - * In this case, any {@code getCache} requests not handled by the configured cache managers will + * Indicate whether a {@link NoOpCacheManager} should be added at the end of the delegate list. + * In this case, any {@code getCache} requests not handled by the configured CacheManagers will * be automatically handled by the {@link NoOpCacheManager} (and hence never return {@code null}). */ public void setFallbackToNoOpCache(boolean fallbackToNoOpCache) { @@ -77,11 +108,11 @@ public Cache getCache(String name) { } public Collection getCacheNames() { - List names = new ArrayList(); + Set names = new LinkedHashSet(); for (CacheManager manager : this.cacheManagers) { names.addAll(manager.getCacheNames()); } - return Collections.unmodifiableList(names); + return Collections.unmodifiableSet(names); } } diff --git a/spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java b/spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java index 9d0bdc4b7c..d65e05058a 100644 --- a/spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.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. @@ -53,6 +53,7 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life * Name of the ConversionService bean in the factory. * If none is supplied, default conversion rules apply. * @see org.springframework.core.convert.ConversionService + * @since 3.0 */ String CONVERSION_SERVICE_BEAN_NAME = "conversionService"; @@ -60,12 +61,14 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life * Name of the LoadTimeWeaver bean in the factory. If such a bean is supplied, * the context will use a temporary ClassLoader for type matching, in order * to allow the LoadTimeWeaver to process all actual bean classes. + * @since 2.5 * @see org.springframework.instrument.classloading.LoadTimeWeaver */ String LOAD_TIME_WEAVER_BEAN_NAME = "loadTimeWeaver"; /** * Name of the {@link Environment} bean in the factory. + * @since 3.1 */ String ENVIRONMENT_BEAN_NAME = "environment"; @@ -84,6 +87,7 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life /** * Set the unique id of this application context. + * @since 3.0 */ void setId(String id); @@ -99,12 +103,14 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life /** * Return the Environment for this application context in configurable form. + * @since 3.1 */ ConfigurableEnvironment getEnvironment(); /** * Set the {@code Environment} for this application context. * @param environment the new environment + * @since 3.1 */ void setEnvironment(ConfigurableEnvironment environment); @@ -112,9 +118,9 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life * Add a new BeanFactoryPostProcessor that will get applied to the internal * bean factory of this application context on refresh, before any of the * bean definitions get evaluated. To be invoked during context configuration. - * @param beanFactoryPostProcessor the factory processor to register + * @param postProcessor the factory processor to register */ - void addBeanFactoryPostProcessor(BeanFactoryPostProcessor beanFactoryPostProcessor); + void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor); /** * Add a new ApplicationListener that will be notified on context events diff --git a/spring-context/src/main/java/org/springframework/context/Lifecycle.java b/spring-context/src/main/java/org/springframework/context/Lifecycle.java index cfc101e39b..4d52031dcd 100644 --- a/spring-context/src/main/java/org/springframework/context/Lifecycle.java +++ b/spring-context/src/main/java/org/springframework/context/Lifecycle.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 the original author 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,13 +17,15 @@ package org.springframework.context; /** - * Interface defining methods for start/stop lifecycle control. + * A common interface defining methods for start/stop lifecycle control. * The typical use case for this is to control asynchronous processing. + * NOTE: This interface does not imply specific auto-startup semantics. + * Consider implementing {@link SmartLifecycle} for that purpose. * - *

Can be implemented by both components (typically a Spring bean defined in - * a Spring {@link org.springframework.beans.factory.BeanFactory}) and containers - * (typically a Spring {@link ApplicationContext}). Containers will propagate - * start/stop signals to all components that apply. + *

Can be implemented by both components (typically a Spring bean defined in a + * Spring context) and containers (typically a Spring {@link ApplicationContext} + * itself). Containers will propagate start/stop signals to all components that + * apply within each container, e.g. for a stop/restart scenario at runtime. * *

Can be used for direct invocations or for management operations via JMX. * In the latter case, the {@link org.springframework.jmx.export.MBeanExporter} @@ -32,10 +34,10 @@ * restricting the visibility of activity-controlled components to the Lifecycle * interface. * - *

Note that the Lifecycle interface is only supported on top-level singleton beans. - * On any other component, the Lifecycle interface will remain undetected and hence ignored. - * Also, note that the extended {@link SmartLifecycle} interface provides more sophisticated - * integration with the container's startup and shutdown phases. + *

Note that the Lifecycle interface is only supported on top-level singleton + * beans. On any other component, the Lifecycle interface will remain undetected + * and hence ignored. Also, note that the extended {@link SmartLifecycle} interface + * provides integration with the application context's startup and shutdown phases. * * @author Juergen Hoeller * @since 2.0 @@ -51,6 +53,7 @@ public interface Lifecycle { * Should not throw an exception if the component is already running. *

In the case of a container, this will propagate the start signal * to all components that apply. + * @see SmartLifecycle#isAutoStartup() */ void start(); diff --git a/spring-context/src/main/java/org/springframework/context/SmartLifecycle.java b/spring-context/src/main/java/org/springframework/context/SmartLifecycle.java index ffd4c92cb9..1ae2c12e9d 100644 --- a/spring-context/src/main/java/org/springframework/context/SmartLifecycle.java +++ b/spring-context/src/main/java/org/springframework/context/SmartLifecycle.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2015 the original author 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,8 +17,8 @@ package org.springframework.context; /** - * An extension of the {@link Lifecycle} interface for those objects that require to be - * started upon ApplicationContext refresh and/or shutdown in a particular order. + * An extension of the {@link Lifecycle} interface for those objects that require to + * be started upon ApplicationContext refresh and/or shutdown in a particular order. * The {@link #isAutoStartup()} return value indicates whether this object should * be started at the time of a context refresh. The callback-accepting * {@link #stop(Runnable)} method is useful for objects that have an asynchronous @@ -55,26 +55,37 @@ * * @author Mark Fisher * @since 3.0 + * @see LifecycleProcessor + * @see ConfigurableApplicationContext */ public interface SmartLifecycle extends Lifecycle, Phased { /** - * Return whether this Lifecycle component should be started automatically - * by the container when the ApplicationContext is refreshed. A value of - * "false" indicates that the component is intended to be started manually. + * Returns {@code true} if this {@code Lifecycle} component should get + * started automatically by the container at the time that the containing + * {@link ApplicationContext} gets refreshed. + *

A value of {@code false} indicates that the component is intended to + * be started through an explicit {@link #start()} call instead, analogous + * to a plain {@link Lifecycle} implementation. + * @see #start() + * @see #getPhase() + * @see LifecycleProcessor#onRefresh() + * @see ConfigurableApplicationContext#refresh() */ boolean isAutoStartup(); /** * Indicates that a Lifecycle component must stop if it is currently running. - *

The provided callback is used by the {@link LifecycleProcessor} to support an - * ordered, and potentially concurrent, shutdown of all components having a + *

The provided callback is used by the {@link LifecycleProcessor} to support + * an ordered, and potentially concurrent, shutdown of all components having a * common shutdown order value. The callback must be executed after - * the SmartLifecycle component does indeed stop. - *

The {@code LifecycleProcessor} will call only this variant of the + * the {@code SmartLifecycle} component does indeed stop. + *

The {@link LifecycleProcessor} will call only this variant of the * {@code stop} method; i.e. {@link Lifecycle#stop()} will not be called for - * {@link SmartLifecycle} implementations unless explicitly delegated to within - * this method. + * {@code SmartLifecycle} implementations unless explicitly delegated to within + * the implementation of this method. + * @see #stop() + * @see #getPhase() */ void stop(Runnable callback); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AdviceModeImportSelector.java b/spring-context/src/main/java/org/springframework/context/annotation/AdviceModeImportSelector.java index eae51ec251..c736bdad57 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AdviceModeImportSelector.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AdviceModeImportSelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,8 +23,6 @@ import org.springframework.core.type.AnnotationMetadata; import org.springframework.util.Assert; -import static org.springframework.context.annotation.MetadataUtils.*; - /** * Convenient base class for {@link ImportSelector} implementations that select imports * based on an {@link AdviceMode} value from an annotation (such as the {@code @Enable*} @@ -40,6 +38,7 @@ public abstract class AdviceModeImportSelector implements public static final String DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME = "mode"; + /** * The name of the {@link AdviceMode} attribute for the annotation specified by the * generic type {@code A}. The default is {@value #DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME}, @@ -50,47 +49,37 @@ protected String getAdviceModeAttributeName() { } /** - * {@inheritDoc} - * - *

This implementation resolves the type of annotation from generic metadata and + * This implementation resolves the type of annotation from generic metadata and * validates that (a) the annotation is in fact present on the importing * {@code @Configuration} class and (b) that the given annotation has an * {@linkplain #getAdviceModeAttributeName() advice mode attribute} of type * {@link AdviceMode}. - * *

The {@link #selectImports(AdviceMode)} method is then invoked, allowing the * concrete implementation to choose imports in a safe and convenient fashion. - * * @throws IllegalArgumentException if expected annotation {@code A} is not present * on the importing {@code @Configuration} class or if {@link #selectImports(AdviceMode)} * returns {@code null} */ public final String[] selectImports(AnnotationMetadata importingClassMetadata) { - Class annoType = GenericTypeResolver.resolveTypeArgument(this.getClass(), AdviceModeImportSelector.class); - - AnnotationAttributes attributes = attributesFor(importingClassMetadata, annoType); + Class annoType = GenericTypeResolver.resolveTypeArgument(getClass(), AdviceModeImportSelector.class); + AnnotationAttributes attributes = MetadataUtils.attributesFor(importingClassMetadata, annoType); Assert.notNull(attributes, String.format( "@%s is not present on importing class '%s' as expected", annoType.getSimpleName(), importingClassMetadata.getClassName())); AdviceMode adviceMode = attributes.getEnum(this.getAdviceModeAttributeName()); - String[] imports = selectImports(adviceMode); Assert.notNull(imports, String.format("Unknown AdviceMode: '%s'", adviceMode)); - return imports; } /** * Determine which classes should be imported based on the given {@code AdviceMode}. - * *

Returning {@code null} from this method indicates that the {@code AdviceMode} could * not be handled or was unknown and that an {@code IllegalArgumentException} should * be thrown. - * * @param adviceMode the value of the {@linkplain #getAdviceModeAttributeName() * advice mode attribute} for the annotation specified via generics. - * * @return array containing classes to import; empty array if none, {@code null} if * the given {@code AdviceMode} is unknown. */ diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java index c09600998c..a0f8312aa7 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,13 +88,16 @@ protected String determineBeanNameFromAnnotation(AnnotatedBeanDefinition annotat for (String type : types) { AnnotationAttributes attributes = MetadataUtils.attributesFor(amd, type); if (isStereotypeWithNameValue(type, amd.getMetaAnnotationTypes(type), attributes)) { - String value = (String) attributes.get("value"); - if (StringUtils.hasLength(value)) { - if (beanName != null && !value.equals(beanName)) { - throw new IllegalStateException("Stereotype annotations suggest inconsistent " + - "component names: '" + beanName + "' versus '" + value + "'"); + Object value = attributes.get("value"); + if (value instanceof String) { + String strVal = (String) value; + if (StringUtils.hasLength(strVal)) { + if (beanName != null && !strVal.equals(beanName)) { + throw new IllegalStateException("Stereotype annotations suggest inconsistent " + + "component names: '" + beanName + "' versus '" + strVal + "'"); + } + beanName = strVal; } - beanName = value; } } } @@ -116,6 +119,7 @@ protected boolean isStereotypeWithNameValue(String annotationType, (metaAnnotationTypes != null && metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME)) || annotationType.equals("javax.annotation.ManagedBean") || annotationType.equals("javax.inject.Named"); + return (isStereotype && attributes != null && attributes.containsKey("value")); } @@ -135,7 +139,7 @@ protected String buildDefaultBeanName(BeanDefinition definition, BeanDefinitionR *

The default implementation simply builds a decapitalized version * of the short class name: e.g. "mypackage.MyJdbcDao" -> "myJdbcDao". *

Note that inner classes will thus have names of the form - * "outerClassName.innerClassName", which because of the period in the + * "outerClassName.InnerClassName", which because of the period in the * name may be an issue if you are autowiring by name. * @param definition the bean definition to build a bean name for * @return the default bean name (never {@code null}) diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java index 4576ab3bba..9110189f73 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java @@ -109,7 +109,7 @@ public void setEnvironment(ConfigurableEnvironment environment) { public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) { this.reader.setBeanNameGenerator(beanNameGenerator); this.scanner.setBeanNameGenerator(beanNameGenerator); - this.getBeanFactory().registerSingleton( + getBeanFactory().registerSingleton( AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator); } @@ -126,9 +126,9 @@ public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver /** * Register one or more annotated classes to be processed. - * Note that {@link #refresh()} must be called in order for the context to fully - * process the new class. - *

Calls to {@link #register} are idempotent; adding the same + * Note that {@link #refresh()} must be called in order for the context + * to fully process the new class. + *

Calls to {@code register} are idempotent; adding the same * annotated class more than once has no additional effect. * @param annotatedClasses one or more annotated classes, * e.g. {@link Configuration @Configuration} classes diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java index f283bb6d2c..ae5a27fba0 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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.core.type.AnnotationMetadata; import org.springframework.util.ClassUtils; -import static org.springframework.context.annotation.MetadataUtils.*; - /** * Utility class that allows for convenient registration of common * {@link org.springframework.beans.factory.config.BeanPostProcessor} and @@ -85,70 +83,89 @@ public class AnnotationConfigUtils { public static final String COMMON_ANNOTATION_PROCESSOR_BEAN_NAME = "org.springframework.context.annotation.internalCommonAnnotationProcessor"; + /** + * The bean name of the internally managed JPA annotation processor. + */ + public static final String PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME = + "org.springframework.context.annotation.internalPersistenceAnnotationProcessor"; + + + private static final String PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME = + "org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"; + + /** * The bean name of the internally managed Scheduled annotation processor. + *

ATTENTION:

This constant is meant for internal use only. The value is stable + * but don't rely on the presence of this constant declaration; rather copy the value. */ public static final String SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME = "org.springframework.context.annotation.internalScheduledAnnotationProcessor"; /** * The bean name of the internally managed Async annotation processor. + *

ATTENTION:

This constant is meant for internal use only. The value is stable + * but don't rely on the presence of this constant declaration; rather copy the value. */ public static final String ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME = "org.springframework.context.annotation.internalAsyncAnnotationProcessor"; /** * The bean name of the internally managed AspectJ async execution aspect. + *

ATTENTION:

This constant is meant for internal use only. The value is stable + * but don't rely on the presence of this constant declaration; rather copy the value. */ public static final String ASYNC_EXECUTION_ASPECT_BEAN_NAME = "org.springframework.scheduling.config.internalAsyncExecutionAspect"; /** * The class name of the AspectJ async execution aspect. + *

ATTENTION:

This constant is meant for internal use only. The value is stable + * but don't rely on the presence of this constant declaration; rather copy the value. */ public static final String ASYNC_EXECUTION_ASPECT_CLASS_NAME = "org.springframework.scheduling.aspectj.AnnotationAsyncExecutionAspect"; /** * The name of the AspectJ async execution aspect @{@code Configuration} class. + *

ATTENTION:

This constant is meant for internal use only. The value is stable + * but don't rely on the presence of this constant declaration; rather copy the value. */ public static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME = "org.springframework.scheduling.aspectj.AspectJAsyncConfiguration"; /** * The bean name of the internally managed cache advisor. + *

ATTENTION:

This constant is meant for internal use only. The value is stable + * but don't rely on the presence of this constant declaration; rather copy the value. */ public static final String CACHE_ADVISOR_BEAN_NAME = "org.springframework.cache.config.internalCacheAdvisor"; /** * The bean name of the internally managed cache aspect. + *

ATTENTION:

This constant is meant for internal use only. The value is stable + * but don't rely on the presence of this constant declaration; rather copy the value. */ public static final String CACHE_ASPECT_BEAN_NAME = "org.springframework.cache.config.internalCacheAspect"; /** * The class name of the AspectJ caching aspect. + *

ATTENTION:

This constant is meant for internal use only. The value is stable + * but don't rely on the presence of this constant declaration; rather copy the value. */ public static final String CACHE_ASPECT_CLASS_NAME = "org.springframework.cache.aspectj.AnnotationCacheAspect"; /** * The name of the AspectJ caching aspect @{@code Configuration} class. + *

ATTENTION:

This constant is meant for internal use only. The value is stable + * but don't rely on the presence of this constant declaration; rather copy the value. */ public static final String CACHE_ASPECT_CONFIGURATION_CLASS_NAME = "org.springframework.cache.aspectj.AspectJCachingConfiguration"; - /** - * The bean name of the internally managed JPA annotation processor. - */ - public static final String PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME = - "org.springframework.context.annotation.internalPersistenceAnnotationProcessor"; - - - private static final String PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME = - "org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"; - private static final boolean jsr250Present = ClassUtils.isPresent("javax.annotation.Resource", AnnotationConfigUtils.class.getClassLoader()); @@ -208,8 +225,8 @@ public static Set registerAnnotationConfigProcessors( if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(); try { - ClassLoader cl = AnnotationConfigUtils.class.getClassLoader(); - def.setBeanClass(cl.loadClass(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME)); + def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, + AnnotationConfigUtils.class.getClassLoader())); } catch (ClassNotFoundException ex) { throw new IllegalStateException( @@ -230,20 +247,20 @@ private static BeanDefinitionHolder registerPostProcessor( return new BeanDefinitionHolder(definition, beanName); } - static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd) { + public static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd) { AnnotationMetadata metadata = abd.getMetadata(); if (metadata.isAnnotated(Primary.class.getName())) { abd.setPrimary(true); } if (metadata.isAnnotated(Lazy.class.getName())) { - abd.setLazyInit(attributesFor(metadata, Lazy.class).getBoolean("value")); + abd.setLazyInit(MetadataUtils.attributesFor(metadata, Lazy.class).getBoolean("value")); } if (metadata.isAnnotated(DependsOn.class.getName())) { - abd.setDependsOn(attributesFor(metadata, DependsOn.class).getStringArray("value")); + abd.setDependsOn(MetadataUtils.attributesFor(metadata, DependsOn.class).getStringArray("value")); } if (abd instanceof AbstractBeanDefinition) { if (metadata.isAnnotated(Role.class.getName())) { - Integer role = attributesFor(metadata, Role.class).getNumber("value"); + Integer role = MetadataUtils.attributesFor(metadata, Role.class).getNumber("value"); ((AbstractBeanDefinition)abd).setRole(role); } } @@ -260,5 +277,4 @@ static BeanDefinitionHolder applyScopedProxyMode( return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass); } - } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java b/spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java index 5fcd8a572d..11b3e7f5d6 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java @@ -1,5 +1,5 @@ - /* - * Copyright 2002-2012 the original author or authors. +/* + * Copyright 2002-2013 the original author 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,12 +16,11 @@ package org.springframework.context.annotation; -import static org.springframework.context.annotation.MetadataUtils.attributesFor; - import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.aop.config.AopConfigUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.core.annotation.AnnotationAttributes; @@ -33,8 +32,8 @@ * {@code proxyTargetClass} attributes set to the correct values. * * @author Chris Beams - * @see EnableAspectJAutoProxy * @since 3.1 + * @see EnableAspectJAutoProxy */ public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar { @@ -47,7 +46,6 @@ public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar { * attributes. If {@code mode} is set to {@code PROXY}, the APC is registered; if * {@code proxyTargetClass} is set to {@code true}, then the APC is forced to use * subclass (CGLIB) proxying. - * *

Several {@code @Enable*} annotations expose both {@code mode} and * {@code proxyTargetClass} attributes. It is important to note that most of these * capabilities end up sharing a {@linkplain AopConfigUtils#AUTO_PROXY_CREATOR_BEAN_NAME @@ -60,16 +58,15 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B boolean candidateFound = false; Set annoTypes = importingClassMetadata.getAnnotationTypes(); for (String annoType : annoTypes) { - AnnotationAttributes candidate = attributesFor(importingClassMetadata, annoType); + AnnotationAttributes candidate = MetadataUtils.attributesFor(importingClassMetadata, annoType); Object mode = candidate.get("mode"); Object proxyTargetClass = candidate.get("proxyTargetClass"); - if (mode != null && proxyTargetClass != null - && mode.getClass().equals(AdviceMode.class) - && proxyTargetClass.getClass().equals(Boolean.class)) { + if (mode != null && proxyTargetClass != null && mode.getClass().equals(AdviceMode.class) && + proxyTargetClass.getClass().equals(Boolean.class)) { candidateFound = true; if (mode == AdviceMode.PROXY) { AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); - if ((Boolean)proxyTargetClass) { + if ((Boolean) proxyTargetClass) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); return; } @@ -81,11 +78,12 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B logger.warn(String.format("%s was imported but no annotations were found " + "having both 'mode' and 'proxyTargetClass' attributes of type " + "AdviceMode and boolean respectively. This means that auto proxy " + - "creator registration and configuration may not have occured as " + + "creator registration and configuration may not have occurred as " + "intended, and components may not be proxied as expected. Check to " + "ensure that %s has been @Import'ed on the same class where these " + "annotations are declared; otherwise remove the import of %s " + "altogether.", name, name, name)); } } + } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Bean.java b/spring-context/src/main/java/org/springframework/context/annotation/Bean.java index 679352decf..8cbaa0da3b 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Bean.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Bean.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. @@ -39,7 +39,8 @@ * public MyBean myBean() { * // instantiate and configure MyBean obj * return obj; - * }

+ * } + * * *

Bean Names

* @@ -55,7 +56,8 @@ * public MyBean myBean() { * // instantiate and configure MyBean obj * return obj; - * } + * } + * * *

Scope, DependsOn, Primary, and Lazy

* @@ -70,7 +72,8 @@ * public MyBean myBean() { * // instantiate and configure MyBean obj * return obj; - * } + * } + * * *

{@code @Bean} Methods in {@code @Configuration} Classes

* @@ -87,14 +90,17 @@ *
  * @Configuration
  * public class AppConfig {
+ *
  *     @Bean
  *     public FooService fooService() {
  *         return new FooService(fooRepository());
  *     }
+ *
  *     @Bean
  *     public FooRepository fooRepository() {
  *         return new JdbcFooRepository(dataSource());
  *     }
+ *
  *     // ...
  * }
* @@ -152,7 +158,8 @@ * @Bean * public static PropertyPlaceholderConfigurer ppc() { * // instantiate, configure and return ppc ... - * } + * } + * * * By marking this method as {@code static}, it can be invoked without causing instantiation of its * declaring {@code @Configuration} class, thus avoiding the above-mentioned lifecycle conflicts. @@ -191,31 +198,39 @@ /** * Are dependencies to be injected via convention-based autowiring by name or type? + *

Note that this autowire mode is just about externally driven autowiring based + * on bean property setter methods by convention, analogous to XML bean definitions. + *

The default mode does allow for annotation-driven autowiring. "no" refers to + * externally driven autowiring only, not affecting any autowiring demands that the + * bean class itself expresses through annotations. + * @see Autowire#BY_NAME + * @see Autowire#BY_TYPE */ Autowire autowire() default Autowire.NO; /** * The optional name of a method to call on the bean instance during initialization. * Not commonly used, given that the method may be called programmatically directly - * within the body of a Bean-annotated method. Default value is {@code ""}, indicating - * that no init method should be called. + * within the body of a Bean-annotated method. + *

The default value is {@code ""}, indicating no init method to be called. */ String initMethod() default ""; /** * The optional name of a method to call on the bean instance upon closing the - * application context, for example a {@code close()} method on a JDBC {@code - * DataSource} implementation, or a Hibernate {@code SessionFactory} object. + * application context, for example a {@code close()} method on a JDBC + * {@code DataSource} implementation, or a Hibernate {@code SessionFactory} object. * The method must have no arguments but may throw any exception. *

As a convenience to the user, the container will attempt to infer a destroy - * method against an object returned from the {@code @Bean} method. For example, given a - * {@code @Bean} method returning an Apache Commons DBCP {@code BasicDataSource}, the - * container will notice the {@code close()} method available on that object and + * method against an object returned from the {@code @Bean} method. For example, given + * an {@code @Bean} method returning an Apache Commons DBCP {@code BasicDataSource}, + * the container will notice the {@code close()} method available on that object and * automatically register it as the {@code destroyMethod}. This 'destroy method * inference' is currently limited to detecting only public, no-arg methods named - * 'close'. The method may be declared at any level of the inheritance hierarchy and - * will be detected regardless of the return type of the {@code @Bean} method (i.e., - * detection occurs reflectively against the bean instance itself at creation time). + * 'close' or 'shutdown'. The method may be declared at any level of the inheritance + * hierarchy and will be detected regardless of the return type of the {@code @Bean} + * method (i.e., detection occurs reflectively against the bean instance itself at + * creation time). *

To disable destroy method inference for a particular {@code @Bean}, specify an * empty string as the value, e.g. {@code @Bean(destroyMethod="")}. Note that the * {@link org.springframework.beans.factory.DisposableBean} and the diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java index b3f9450f98..e766e24880 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -167,7 +167,7 @@ public void setBeanDefinitionDefaults(BeanDefinitionDefaults beanDefinitionDefau * Set the name-matching patterns for determining autowire candidates. * @param autowireCandidatePatterns the patterns to match against */ - public void setAutowireCandidatePatterns(String[] autowireCandidatePatterns) { + public void setAutowireCandidatePatterns(String... autowireCandidatePatterns) { this.autowireCandidatePatterns = autowireCandidatePatterns; } @@ -224,7 +224,7 @@ public int scan(String... basePackages) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } - return this.registry.getBeanDefinitionCount() - beanCountAtScanStart; + return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart); } /** @@ -317,8 +317,8 @@ protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) /** * Determine whether the given new bean definition is compatible with * the given existing bean definition. - *

The default implementation simply considers them as compatible - * when the bean class name matches. + *

The default implementation considers them as compatible when the existing + * bean definition comes from the same source or from a non-scanning source. * @param newDefinition the new bean definition, originated from scanning * @param existingDefinition the existing bean definition, potentially an * explicitly defined one or a previously generated one from scanning diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java index 6dacb49d60..9714322ba7 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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 class ClassPathScanningCandidateComponentProvider implements EnvironmentC /** - * Create a ClassPathScanningCandidateComponentProvider. + * Create a ClassPathScanningCandidateComponentProvider with a {@link StandardEnvironment}. * @param useDefaultFilters whether to register the default filters for the * {@link Component @Component}, {@link Repository @Repository}, * {@link Service @Service}, and {@link Controller @Controller} @@ -101,6 +101,15 @@ public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters) { this(useDefaultFilters, new StandardEnvironment()); } + /** + * Create a ClassPathScanningCandidateComponentProvider with the given {@link Environment}. + * @param useDefaultFilters whether to register the default filters for the + * {@link Component @Component}, {@link Repository @Repository}, + * {@link Service @Service}, and {@link Controller @Controller} + * stereotype annotations + * @param environment the Environment to use + * @see #registerDefaultFilters() + */ public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters, Environment environment) { if (useDefaultFilters) { registerDefaultFilters(); @@ -218,16 +227,16 @@ protected void registerDefaultFilters() { ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader(); try { this.includeFilters.add(new AnnotationTypeFilter( - ((Class) cl.loadClass("javax.annotation.ManagedBean")), false)); - logger.info("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning"); + ((Class) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false)); + logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning"); } catch (ClassNotFoundException ex) { // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip. } try { this.includeFilters.add(new AnnotationTypeFilter( - ((Class) cl.loadClass("javax.inject.Named")), false)); - logger.info("JSR-330 'javax.inject.Named' annotation found and supported for component scanning"); + ((Class) ClassUtils.forName("javax.inject.Named", cl)), false)); + logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning"); } catch (ClassNotFoundException ex) { // JSR-330 API not available - simply skip. diff --git a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java index 93a71336a0..3e9a74ee72 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -145,10 +145,10 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean private static Class ejbRefClass = null; static { - ClassLoader cl = CommonAnnotationBeanPostProcessor.class.getClassLoader(); try { @SuppressWarnings("unchecked") - Class clazz = (Class) cl.loadClass("javax.xml.ws.WebServiceRef"); + Class clazz = (Class) + ClassUtils.forName("javax.xml.ws.WebServiceRef", CommonAnnotationBeanPostProcessor.class.getClassLoader()); webServiceRefClass = clazz; } catch (ClassNotFoundException ex) { @@ -156,7 +156,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean } try { @SuppressWarnings("unchecked") - Class clazz = (Class) cl.loadClass("javax.ejb.EJB"); + Class clazz = (Class) + ClassUtils.forName("javax.ejb.EJB", CommonAnnotationBeanPostProcessor.class.getClassLoader()); ejbRefClass = clazz; } catch (ClassNotFoundException ex) { @@ -177,8 +178,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean private transient BeanFactory beanFactory; - private transient final Map, InjectionMetadata> injectionMetadataCache = - new ConcurrentHashMap, InjectionMetadata>(64); + private transient final Map injectionMetadataCache = + new ConcurrentHashMap(64); /** @@ -282,7 +283,7 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException { public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) { super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName); if (beanType != null) { - InjectionMetadata metadata = findResourceMetadata(beanType); + InjectionMetadata metadata = findResourceMetadata(beanName, beanType, null); metadata.checkConfigMembers(beanDefinition); } } @@ -298,7 +299,7 @@ public boolean postProcessAfterInstantiation(Object bean, String beanName) throw public PropertyValues postProcessPropertyValues( PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { - InjectionMetadata metadata = findResourceMetadata(bean.getClass()); + InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs); try { metadata.inject(bean, beanName, pvs); } @@ -309,13 +310,19 @@ public PropertyValues postProcessPropertyValues( } - private InjectionMetadata findResourceMetadata(final Class clazz) { + private InjectionMetadata findResourceMetadata(String beanName, final Class clazz, PropertyValues pvs) { + // Fall back to class name as cache key, for backwards compatibility with custom callers. + String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName()); // Quick check on the concurrent map first, with minimal locking. - InjectionMetadata metadata = this.injectionMetadataCache.get(clazz); - if (metadata == null) { + InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey); + if (InjectionMetadata.needsRefresh(metadata, clazz)) { synchronized (this.injectionMetadataCache) { - metadata = this.injectionMetadataCache.get(clazz); - if (metadata == null) { + metadata = this.injectionMetadataCache.get(cacheKey); + if (InjectionMetadata.needsRefresh(metadata, clazz)) { + if (metadata != null) { + metadata.clear(pvs); + } + LinkedList elements = new LinkedList(); Class targetClass = clazz; @@ -354,7 +361,7 @@ else if (field.isAnnotationPresent(Resource.class)) { if (method.getParameterTypes().length != 1) { throw new IllegalStateException("@WebServiceRef annotation requires a single-arg method: " + method); } - PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method); + PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz); currElements.add(new WebServiceRefElement(method, pd)); } else if (ejbRefClass != null && method.isAnnotationPresent(ejbRefClass)) { @@ -364,7 +371,7 @@ else if (ejbRefClass != null && method.isAnnotationPresent(ejbRefClass)) { if (method.getParameterTypes().length != 1) { throw new IllegalStateException("@EJB annotation requires a single-arg method: " + method); } - PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method); + PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz); currElements.add(new EjbRefElement(method, pd)); } else if (method.isAnnotationPresent(Resource.class)) { @@ -376,7 +383,7 @@ else if (method.isAnnotationPresent(Resource.class)) { throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method); } if (!ignoredResourceTypes.contains(paramTypes[0].getName())) { - PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method); + PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method, clazz); currElements.add(new ResourceElement(method, pd)); } } @@ -388,7 +395,7 @@ else if (method.isAnnotationPresent(Resource.class)) { while (targetClass != null && targetClass != Object.class); metadata = new InjectionMetadata(clazz, elements); - this.injectionMetadataCache.put(clazz, metadata); + this.injectionMetadataCache.put(cacheKey, metadata); } } } @@ -472,11 +479,8 @@ protected abstract class LookupElement extends InjectionMetadata.InjectedElement public LookupElement(Member member, PropertyDescriptor pd) { super(member, pd); - initAnnotation((AnnotatedElement) member); } - protected abstract void initAnnotation(AnnotatedElement ae); - /** * Return the resource name for the lookup. */ @@ -511,14 +515,11 @@ public final DependencyDescriptor getDependencyDescriptor() { */ private class ResourceElement extends LookupElement { - protected boolean shareable = true; + protected final boolean shareable; public ResourceElement(Member member, PropertyDescriptor pd) { super(member, pd); - } - - @Override - protected void initAnnotation(AnnotatedElement ae) { + AnnotatedElement ae = (AnnotatedElement) member; Resource resource = ae.getAnnotation(Resource.class); String resourceName = resource.name(); Class resourceType = resource.type(); @@ -558,16 +559,13 @@ protected Object getResourceToInject(Object target, String requestingBeanName) { */ private class WebServiceRefElement extends LookupElement { - private Class elementType; + private final Class elementType; - private String wsdlLocation; + private final String wsdlLocation; public WebServiceRefElement(Member member, PropertyDescriptor pd) { super(member, pd); - } - - @Override - protected void initAnnotation(AnnotatedElement ae) { + AnnotatedElement ae = (AnnotatedElement) member; WebServiceRef resource = ae.getAnnotation(WebServiceRef.class); String resourceName = resource.name(); Class resourceType = resource.type(); @@ -613,7 +611,7 @@ protected Object getResourceToInject(Object target, String requestingBeanName) { } if (StringUtils.hasLength(this.wsdlLocation)) { try { - Constructor ctor = this.lookupType.getConstructor(new Class[] {URL.class, QName.class}); + Constructor ctor = this.lookupType.getConstructor(new Class[] {URL.class, QName.class}); WebServiceClient clientAnn = this.lookupType.getAnnotation(WebServiceClient.class); if (clientAnn == null) { throw new IllegalStateException("JAX-WS Service class [" + this.lookupType.getName() + @@ -647,14 +645,11 @@ protected Object getResourceToInject(Object target, String requestingBeanName) { */ private class EjbRefElement extends LookupElement { - private String beanName; + private final String beanName; public EjbRefElement(Member member, PropertyDescriptor pd) { super(member, pd); - } - - @Override - protected void initAnnotation(AnnotatedElement ae) { + AnnotatedElement ae = (AnnotatedElement) member; EJB resource = ae.getAnnotation(EJB.class); String resourceBeanName = resource.beanName(); String resourceName = resource.name(); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java index eaab654ec7..f90f4d9858 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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 @@ * always registered, meaning that any attempt to disable them at the * {@code @ComponentScan} level would be ignored. * - *

See @{@link Configuration} Javadoc for usage examples. + *

See @{@link Configuration}'s javadoc for usage examples. * * @author Chris Beams * @since 3.1 @@ -139,24 +139,18 @@ @Retention(RetentionPolicy.RUNTIME) @Target({}) @interface Filter { + /** - * The type of filter to use. - *

Note that the filter types available are limited to those that may - * be expressed as a {@code Class} in the {@link #value()} attribute. This is - * in contrast to {@code }, which allows for - * expression-based (i.e., string-based) filters such as AspectJ pointcuts. - * These filter types are intentionally not supported here, and not available - * in the {@link FilterType} enum. - * @see FilterType + * The type of filter to use. Default is {@link FilterType#ANNOTATION}. */ FilterType type() default FilterType.ANNOTATION; /** * The class or classes to use as the filter. In the case of - * {@link FilterType#ANNOTATION}, the class will be the annotation itself. In the - * case of {@link FilterType#ASSIGNABLE_TYPE}, the class will be the type that - * detected components should be assignable to. And in the case of - * {@link FilterType#CUSTOM}, the class will be an implementation of + * {@link FilterType#ANNOTATION}, the class will be the annotation itself. + * In the case of {@link FilterType#ASSIGNABLE_TYPE}, the class will be the + * type that detected components should be assignable to. And in the case + * of {@link FilterType#CUSTOM}, the class will be an implementation of * {@link TypeFilter}. *

When multiple classes are specified, OR logic is applied, e.g. "include * types annotated with {@code @Foo} OR {@code @Bar}". diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java index 48af4ed5a0..7519495de0 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.core.type.filter.TypeFilter; @@ -64,7 +65,7 @@ public ComponentScanAnnotationParser(ResourceLoader resourceLoader, Environment } - public Set parse(AnnotationAttributes componentScan, String declaringClass) { + public Set parse(AnnotationAttributes componentScan, final String declaringClass) { ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters")); @@ -120,6 +121,12 @@ public Set parse(AnnotationAttributes componentScan, Strin basePackages.add(ClassUtils.getPackageName(declaringClass)); } + scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) { + @Override + protected boolean matchClassName(String className) { + return declaringClass.equals(className); + } + }); return scanner.doScan(StringUtils.toStringArray(basePackages)); } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanBeanDefinitionParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanBeanDefinitionParser.java index f7b5677635..4893c0d74e 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanBeanDefinitionParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -239,7 +239,7 @@ else if ("regex".equals(filterType)) { return new RegexPatternTypeFilter(Pattern.compile(expression)); } else if ("custom".equals(filterType)) { - Class filterClass = classLoader.loadClass(expression); + Class filterClass = classLoader.loadClass(expression); if (!TypeFilter.class.isAssignableFrom(filterClass)) { throw new IllegalArgumentException( "Class is not assignable to [" + TypeFilter.class.getName() + "]: " + expression); @@ -256,8 +256,8 @@ else if ("custom".equals(filterType)) { } @SuppressWarnings("unchecked") - private Object instantiateUserDefinedStrategy(String className, Class strategyType, ClassLoader classLoader) { - Object result = null; + private Object instantiateUserDefinedStrategy(String className, Class strategyType, ClassLoader classLoader) { + Object result; try { result = classLoader.loadClass(className).newInstance(); } @@ -267,7 +267,7 @@ private Object instantiateUserDefinedStrategy(String className, Class strategyTy } catch (Exception ex) { throw new IllegalArgumentException("Unable to instantiate class [" + className + "] for strategy [" + - strategyType.getName() + "]. A zero-argument constructor is required", ex); + strategyType.getName() + "]: a zero-argument constructor is required", ex); } if (!strategyType.isAssignableFrom(result.getClass())) { diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java index a25fdcf4ce..0b16e6bfca 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,8 +36,8 @@ /** * Represents a user-defined {@link Configuration @Configuration} class. - * Includes a set of {@link Bean} methods, including all such methods defined in the - * ancestry of the class, in a 'flattened-out' manner. + * Includes a set of {@link Bean} methods, including all such methods + * defined in the ancestry of the class, in a 'flattened-out' manner. * * @author Chris Beams * @author Juergen Hoeller @@ -51,15 +51,15 @@ final class ConfigurationClass { private final Resource resource; - private final Map> importedResources = - new LinkedHashMap>(); - - private final Set beanMethods = new LinkedHashSet(); - private String beanName; private final boolean imported; + private final Set beanMethods = new LinkedHashSet(); + + private final Map> importedResources = + new LinkedHashMap>(); + /** * Create a new {@link ConfigurationClass} with the given name. @@ -81,7 +81,7 @@ public ConfigurationClass(MetadataReader metadataReader, String beanName) { * using the {@link Import} annotation or automatically processed as a nested * configuration class (if imported is {@code true}). * @param metadataReader reader used to parse the underlying {@link Class} - * @param beanName name of the {@code @Configuration} class bean + * @param imported whether the given configuration class is being imported * @since 3.1.1 */ public ConfigurationClass(MetadataReader metadataReader, boolean imported) { @@ -98,7 +98,7 @@ public ConfigurationClass(MetadataReader metadataReader, boolean imported) { * @see ConfigurationClass#ConfigurationClass(Class, boolean) */ public ConfigurationClass(Class clazz, String beanName) { - Assert.hasText(beanName, "bean name must not be null"); + Assert.hasText(beanName, "Bean name must not be null"); this.metadata = new StandardAnnotationMetadata(clazz, true); this.resource = new DescriptiveResource(clazz.toString()); this.beanName = beanName; @@ -110,7 +110,7 @@ public ConfigurationClass(Class clazz, String beanName) { * using the {@link Import} annotation or automatically processed as a nested * configuration class (if imported is {@code true}). * @param clazz the underlying {@link Class} to represent - * @param beanName name of the {@code @Configuration} class bean + * @param imported whether the given configuration class is being imported * @since 3.1.1 */ public ConfigurationClass(Class clazz, boolean imported) { @@ -119,6 +119,7 @@ public ConfigurationClass(Class clazz, boolean imported) { this.imported = imported; } + public AnnotationMetadata getMetadata() { return this.metadata; } @@ -131,6 +132,14 @@ public String getSimpleName() { return ClassUtils.getShortName(getMetadata().getClassName()); } + public void setBeanName(String beanName) { + this.beanName = beanName; + } + + public String getBeanName() { + return this.beanName; + } + /** * Return whether this configuration class was registered via @{@link Import} or * automatically registered due to being nested within another configuration class. @@ -140,14 +149,6 @@ public boolean isImported() { return this.imported; } - public void setBeanName(String beanName) { - this.beanName = beanName; - } - - public String getBeanName() { - return this.beanName; - } - public void addBeanMethod(BeanMethod method) { this.beanMethods.add(method); } @@ -156,8 +157,7 @@ public Set getBeanMethods() { return this.beanMethods; } - public void addImportedResource( - String importedResource, Class readerClass) { + public void addImportedResource(String importedResource, Class readerClass) { this.importedResources.put(importedResource, readerClass); } @@ -166,40 +166,35 @@ public Map> getImportedResources() } public void validate(ProblemReporter problemReporter) { + // A configuration class may not be final (CGLIB limitation) + if (getMetadata().isAnnotated(Configuration.class.getName())) { + if (getMetadata().isFinal()) { + problemReporter.error(new FinalConfigurationProblem()); + } + } + // An @Bean method may only be overloaded through inheritance. No single // @Configuration class may declare two @Bean methods with the same name. - final char hashDelim = '#'; Map methodNameCounts = new HashMap(); - for (BeanMethod beanMethod : beanMethods) { - String dClassName = beanMethod.getMetadata().getDeclaringClassName(); - String methodName = beanMethod.getMetadata().getMethodName(); - String fqMethodName = dClassName + hashDelim + methodName; + for (BeanMethod beanMethod : this.beanMethods) { + String fqMethodName = beanMethod.getFullyQualifiedMethodName(); Integer currentCount = methodNameCounts.get(fqMethodName); - int newCount = currentCount != null ? currentCount + 1 : 1; + int newCount = (currentCount != null ? currentCount + 1 : 1); methodNameCounts.put(fqMethodName, newCount); } - - for (String methodName : methodNameCounts.keySet()) { - int count = methodNameCounts.get(methodName); + for (String fqMethodName : methodNameCounts.keySet()) { + int count = methodNameCounts.get(fqMethodName); if (count > 1) { - String shortMethodName = methodName.substring(methodName.indexOf(hashDelim)+1); + String shortMethodName = ConfigurationMethod.getShortMethodName(fqMethodName); problemReporter.error(new BeanMethodOverloadingProblem(shortMethodName, count)); } } - // A configuration class may not be final (CGLIB limitation) - if (getMetadata().isAnnotated(Configuration.class.getName())) { - if (getMetadata().isFinal()) { - problemReporter.error(new FinalConfigurationProblem()); - } - } - for (BeanMethod beanMethod : this.beanMethods) { beanMethod.validate(problemReporter); } } - @Override public boolean equals(Object other) { return (this == other || (other instanceof ConfigurationClass && @@ -213,7 +208,7 @@ public int hashCode() { @Override public String toString() { - return String.format("[ConfigurationClass:beanName=%s,resource=%s]", this.beanName, this.resource); + return "ConfigurationClass:beanName=" + this.beanName + ",resource=" + this.resource; } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java index e1676c6eb2..3824467bae 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java @@ -51,8 +51,6 @@ import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.util.StringUtils; -import static org.springframework.context.annotation.MetadataUtils.*; - /** * Reads a given fully-populated set of ConfigurationClass instances, registering bean * definitions with the given {@link BeanDefinitionRegistry} based on its contents. @@ -134,8 +132,6 @@ private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configC private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) { AnnotationMetadata metadata = configClass.getMetadata(); BeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata); - String className = metadata.getClassName(); - configBeanDef.setBeanClassName(className); if (ConfigurationClassUtils.checkConfigurationClassCandidate(configBeanDef, this.metadataReaderFactory)) { String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry); this.registry.registerBeanDefinition(configBeanName, configBeanDef); @@ -146,7 +142,7 @@ private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationCl } else { this.problemReporter.error( - new InvalidConfigurationImportProblem(className, configClass.getResource(), metadata)); + new InvalidConfigurationImportProblem(metadata.getClassName(), configClass.getResource(), metadata)); } } @@ -158,7 +154,7 @@ private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) { ConfigurationClass configClass = beanMethod.getConfigurationClass(); MethodMetadata metadata = beanMethod.getMetadata(); - RootBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass); + ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass); beanDef.setResource(configClass.getResource()); beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource())); if (metadata.isStatic()) { @@ -175,13 +171,13 @@ private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) { beanDef.setAttribute(RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE); // consider role - AnnotationAttributes role = attributesFor(metadata, Role.class); + AnnotationAttributes role = MetadataUtils.attributesFor(metadata, Role.class); if (role != null) { beanDef.setRole(role.getNumber("value")); } // consider name and any aliases - AnnotationAttributes bean = attributesFor(metadata, Bean.class); + AnnotationAttributes bean = MetadataUtils.attributesFor(metadata, Bean.class); List names = new ArrayList(Arrays.asList(bean.getStringArray("name"))); String beanName = (names.size() > 0 ? names.remove(0) : beanMethod.getMetadata().getMethodName()); for (String alias : names) { @@ -190,9 +186,18 @@ private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) { // has this already been overridden (e.g. via XML)? if (this.registry.containsBeanDefinition(beanName)) { - BeanDefinition existingBeanDef = registry.getBeanDefinition(beanName); - // is the existing bean definition one that was created from a configuration class? - if (!(existingBeanDef instanceof ConfigurationClassBeanDefinition)) { + BeanDefinition existingBeanDef = this.registry.getBeanDefinition(beanName); + // Is the existing bean definition one that was created from a configuration class? + // -> allow the current bean method to override, since both are at second-pass level. + // However, if the bean method is an overloaded case on the same configuration class, + // preserve the existing bean definition. + if (existingBeanDef instanceof ConfigurationClassBeanDefinition) { + ConfigurationClassBeanDefinition ccbd = (ConfigurationClassBeanDefinition) existingBeanDef; + if (ccbd.getMetadata().getClassName().equals(beanMethod.getConfigurationClass().getMetadata().getClassName())) { + return; + } + } + else { // no -> then it's an external override, probably XML // overriding is legal, return immediately if (logger.isDebugEnabled()) { @@ -209,16 +214,16 @@ private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) { // is this bean to be instantiated lazily? if (metadata.isAnnotated(Lazy.class.getName())) { - AnnotationAttributes lazy = attributesFor(metadata, Lazy.class); + AnnotationAttributes lazy = MetadataUtils.attributesFor(metadata, Lazy.class); beanDef.setLazyInit(lazy.getBoolean("value")); } else if (configClass.getMetadata().isAnnotated(Lazy.class.getName())){ - AnnotationAttributes lazy = attributesFor(configClass.getMetadata(), Lazy.class); + AnnotationAttributes lazy = MetadataUtils.attributesFor(configClass.getMetadata(), Lazy.class); beanDef.setLazyInit(lazy.getBoolean("value")); } if (metadata.isAnnotated(DependsOn.class.getName())) { - AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class); + AnnotationAttributes dependsOn = MetadataUtils.attributesFor(metadata, DependsOn.class); String[] otherBeans = dependsOn.getStringArray("value"); if (otherBeans.length > 0) { beanDef.setDependsOn(otherBeans); @@ -240,9 +245,9 @@ else if (configClass.getMetadata().isAnnotated(Lazy.class.getName())){ beanDef.setDestroyMethodName(destroyMethodName); } - // consider scoping + // Consider scoping ScopedProxyMode proxyMode = ScopedProxyMode.NO; - AnnotationAttributes scope = attributesFor(metadata, Scope.class); + AnnotationAttributes scope = MetadataUtils.attributesFor(metadata, Scope.class); if (scope != null) { beanDef.setScope(scope.getString("value")); proxyMode = scope.getEnum("proxyMode"); @@ -251,7 +256,7 @@ else if (configClass.getMetadata().isAnnotated(Lazy.class.getName())){ } } - // replace the original bean definition with the target one, if necessary + // Replace the original bean definition with the target one, if necessary BeanDefinition beanDefToRegister = beanDef; if (proxyMode != ScopedProxyMode.NO) { BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy( @@ -261,10 +266,11 @@ else if (configClass.getMetadata().isAnnotated(Lazy.class.getName())){ } if (logger.isDebugEnabled()) { - logger.debug(String.format("Registering bean definition for @Bean method %s.%s()", configClass.getMetadata().getClassName(), beanName)); + logger.debug(String.format("Registering bean definition for @Bean method %s.%s()", + configClass.getMetadata().getClassName(), beanName)); } - registry.registerBeanDefinition(beanName, beanDefToRegister); + this.registry.registerBeanDefinition(beanName, beanDefToRegister); } @@ -289,7 +295,8 @@ private void loadBeanDefinitionsFromImportedResources( readerInstanceCache.put(readerClass, readerInstance); } catch (Exception ex) { - throw new IllegalStateException("Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]"); + throw new IllegalStateException( + "Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]"); } } BeanDefinitionReader reader = readerInstanceCache.get(readerClass); @@ -312,6 +319,7 @@ private static class ConfigurationClassBeanDefinition extends RootBeanDefinition public ConfigurationClassBeanDefinition(ConfigurationClass configClass) { this.annotationMetadata = configClass.getMetadata(); + setLenientConstructorResolution(false); } public ConfigurationClassBeanDefinition(RootBeanDefinition original, ConfigurationClass configClass) { diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java index a74deefd07..fcc6818f15 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author 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 org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.support.SimpleInstantiationStrategy; +import org.springframework.cglib.core.SpringNamingPolicy; import org.springframework.cglib.proxy.Callback; import org.springframework.cglib.proxy.CallbackFilter; import org.springframework.cglib.proxy.Enhancer; @@ -39,8 +40,13 @@ import org.springframework.util.Assert; /** - * Enhances {@link Configuration} classes by generating a CGLIB subclass capable of - * interacting with the Spring container to respect bean semantics. + * Enhances {@link Configuration} classes by generating a CGLIB subclass which + * interacts with the Spring container to respect bean scoping semantics for + * {@code @Bean} methods. Each such {@code @Bean} method will be overridden in + * the generated subclass, only delegating to the actual {@code @Bean} method + * implementation if the container actually requests the construction of a new + * instance. Otherwise, a call to such an {@code @Bean} method serves as a + * reference back to the container, obtaining the corresponding bean by name. * * @author Chris Beams * @author Juergen Hoeller @@ -50,15 +56,15 @@ */ class ConfigurationClassEnhancer { - private static final Log logger = LogFactory.getLog(ConfigurationClassEnhancer.class); - - private static final CallbackFilter CALLBACK_FILTER = new ConfigurationClassCallbackFilter(); - private static final Callback DISPOSABLE_BEAN_METHOD_INTERCEPTOR = new DisposableBeanMethodInterceptor(); private static final Class[] CALLBACK_TYPES = {BeanMethodInterceptor.class, DisposableBeanMethodInterceptor.class, NoOp.class}; + private static final CallbackFilter CALLBACK_FILTER = new ConfigurationClassCallbackFilter(); + + private static final Log logger = LogFactory.getLog(ConfigurationClassEnhancer.class); + private final Callback[] callbackInstances; @@ -103,8 +109,9 @@ public Class enhance(Class configClass) { private Enhancer newEnhancer(Class superclass) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(superclass); - enhancer.setInterfaces(new Class[] {EnhancedConfiguration.class}); + enhancer.setInterfaces(new Class[] {EnhancedConfiguration.class}); enhancer.setUseFactory(false); + enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); enhancer.setCallbackFilter(CALLBACK_FILTER); enhancer.setCallbackTypes(CALLBACK_TYPES); return enhancer; @@ -116,21 +123,21 @@ private Enhancer newEnhancer(Class superclass) { */ private Class createClass(Enhancer enhancer) { Class subclass = enhancer.createClass(); - // registering callbacks statically (as opposed to threadlocal) is critical for usage in an OSGi env (SPR-5932) + // Registering callbacks statically (as opposed to thread-local) + // is critical for usage in an OSGi environment (SPR-5932)... Enhancer.registerStaticCallbacks(subclass, this.callbackInstances); return subclass; } - /** * Marker interface to be implemented by all @Configuration CGLIB subclasses. * Facilitates idempotent behavior for {@link ConfigurationClassEnhancer#enhance(Class)} * through checking to see if candidate classes are already assignable to it, e.g. * have already been enhanced. - *

Also extends {@link DisposableBean}, as all enhanced - * {@code @Configuration} classes must de-register static CGLIB callbacks on - * destruction, which is handled by the (private) {@code DisposableBeanMethodInterceptor}. + *

Also extends {@link DisposableBean}, as all enhanced {@code @Configuration} + * classes must de-register static CGLIB callbacks on destruction, which is handled + * by the (private) {@code DisposableBeanMethodInterceptor}. *

Note that this interface is intended for framework-internal use only, however * must remain public in order to allow access to subclasses generated from other * packages (i.e. user code). @@ -192,8 +199,8 @@ private static class DisposableBeanMethodInterceptor implements MethodIntercepto public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { Enhancer.registerStaticCallbacks(obj.getClass(), null); - // does the actual (non-CGLIB) superclass actually implement DisposableBean? - // if so, call its dispose() method. If not, just exit. + // Does the actual (non-CGLIB) superclass actually implement DisposableBean? + // If so, call its dispose() method. If not, just exit. if (DisposableBean.class.isAssignableFrom(obj.getClass().getSuperclass())) { return proxy.invokeSuper(obj, args); } @@ -233,16 +240,15 @@ public BeanMethodInterceptor(ConfigurableBeanFactory beanFactory) { /** * Enhance a {@link Bean @Bean} method to check the supplied BeanFactory for the * existence of this bean object. - * @throws Throwable as a catch-all for any exception that may be thrown when - * invoking the super implementation of the proxied method i.e., the actual - * {@code @Bean} method. + * @throws Throwable as a catch-all for any exception that may be thrown when invoking the + * super implementation of the proxied method i.e., the actual {@code @Bean} method */ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs, MethodProxy cglibMethodProxy) throws Throwable { String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod); - // determine whether this bean is a scoped-proxy + // Determine whether this bean is a scoped-proxy Scope scope = AnnotationUtils.findAnnotation(beanMethod, Scope.class); if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) { String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName); @@ -251,27 +257,27 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object } } - // to handle the case of an inter-bean method reference, we must explicitly check the - // container for already cached instances + // To handle the case of an inter-bean method reference, we must explicitly check the + // container for already cached instances. - // first, check to see if the requested bean is a FactoryBean. If so, create a subclass + // First, check to see if the requested bean is a FactoryBean. If so, create a subclass // proxy that intercepts calls to getObject() and returns any cached bean instance. - // this ensures that the semantics of calling a FactoryBean from within @Bean methods + // This ensures that the semantics of calling a FactoryBean from within @Bean methods // is the same as that of referring to a FactoryBean within XML. See SPR-6602. if (factoryContainsBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName) && factoryContainsBean(beanName)) { Object factoryBean = this.beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName); if (factoryBean instanceof ScopedProxyFactoryBean) { - // pass through - scoped proxy factory beans are a special case and should not + // Pass through - scoped proxy factory beans are a special case and should not // be further proxied } else { - // it is a candidate FactoryBean - go ahead with enhancement + // It is a candidate FactoryBean - go ahead with enhancement return enhanceFactoryBean(factoryBean.getClass(), beanName); } } if (isCurrentlyInvokedFactoryMethod(beanMethod) && !this.beanFactory.containsSingleton(beanName)) { - // the factory is calling the bean method in order to instantiate and register the bean + // The factory is calling the bean method in order to instantiate and register the bean // (i.e. via a getBean() call) -> invoke the super implementation of the method to actually // create the bean instance. if (BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) { @@ -280,13 +286,13 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object "result in a failure to process annotations such as @Autowired, " + "@Resource and @PostConstruct within the method's declaring " + "@Configuration class. Add the 'static' modifier to this method to avoid " + - "these container lifecycle issues; see @Bean Javadoc for complete details", + "these container lifecycle issues; see @Bean javadoc for complete details", beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName())); } return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs); } else { - // the user (i.e. not the factory) is requesting this bean through a + // The user (i.e. not the factory) is requesting this bean through a // call to the bean method, direct or indirect. The bean may have already been // marked as 'in creation' in certain autowiring scenarios; if so, temporarily // set the in-creation status to false in order to avoid an exception. @@ -303,7 +309,6 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object } } } - } /** @@ -354,7 +359,7 @@ private Object enhanceFactoryBean(Class fbClass, String beanName) throws Inst }; enhancer.setCallbackTypes(CALLBACK_TYPES); Class fbSubclass = enhancer.createClass(); - Enhancer.registerCallbacks(fbSubclass, callbackInstances); + Enhancer.registerStaticCallbacks(fbSubclass, callbackInstances); return fbSubclass.newInstance(); } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index c03aca61dd..74e2d435b8 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author 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,11 +17,12 @@ package org.springframework.context.annotation; import java.io.IOException; +import java.lang.annotation.Annotation; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; @@ -41,7 +42,9 @@ import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.NestedIOException; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.Environment; @@ -54,11 +57,9 @@ import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.AssignableTypeFilter; -import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; -import static org.springframework.context.annotation.MetadataUtils.*; - /** * Parses a {@link Configuration} class definition, populating a collection of * {@link ConfigurationClass} objects (parsing a single Configuration class may result in @@ -83,16 +84,6 @@ class ConfigurationClassParser { private final ProblemReporter problemReporter; - private final ImportStack importStack = new ImportStack(); - - private final Set knownSuperclasses = new LinkedHashSet(); - - private final Set configurationClasses = - new LinkedHashSet(); - - private final Stack> propertySources = - new Stack>(); - private final Environment environment; private final ResourceLoader resourceLoader; @@ -101,6 +92,14 @@ class ConfigurationClassParser { private final ComponentScanAnnotationParser componentScanParser; + private final Set configurationClasses = new LinkedHashSet(); + + private final Map knownSuperclasses = new HashMap(); + + private final Stack> propertySources = new Stack>(); + + private final ImportStack importStack = new ImportStack(); + /** * Create a new {@link ConfigurationClassParser} instance that will be used @@ -149,77 +148,48 @@ protected void processConfigurationClass(ConfigurationClass configClass) throws } } - // recursively process the configuration class and its superclass hierarchy - do { - metadata = doProcessConfigurationClass(configClass, metadata); - } - while (metadata != null); - if (this.configurationClasses.contains(configClass) && configClass.getBeanName() != null) { // Explicit bean definition found, probably replacing an import. // Let's remove the old one and go with the new one. this.configurationClasses.remove(configClass); + for (Iterator it = this.knownSuperclasses.values().iterator(); it.hasNext();) { + if (configClass.equals(it.next())) { + it.remove(); + } + } } + // Recursively process the configuration class and its superclass hierarchy. + do { + metadata = doProcessConfigurationClass(configClass, metadata); + } + while (metadata != null); + this.configurationClasses.add(configClass); } /** - * @return annotation metadata of superclass, null if none found or previously processed + * @return annotation metadata of superclass, {@code null} if none found or previously processed */ - protected AnnotationMetadata doProcessConfigurationClass( - ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException { - - // recursively process any member (nested) classes first - for (String memberClassName : metadata.getMemberClassNames()) { - MetadataReader reader = this.metadataReaderFactory.getMetadataReader(memberClassName); - AnnotationMetadata memberClassMetadata = reader.getAnnotationMetadata(); - if (ConfigurationClassUtils.isConfigurationCandidate(memberClassMetadata)) { - processConfigurationClass(new ConfigurationClass(reader, true)); - } - } + protected AnnotationMetadata doProcessConfigurationClass(ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException { + // Recursively process any member (nested) classes first + processMemberClasses(metadata); - // process any @PropertySource annotations - AnnotationAttributes propertySource = - attributesFor(metadata, org.springframework.context.annotation.PropertySource.class); + // Process any @PropertySource annotations + AnnotationAttributes propertySource = MetadataUtils.attributesFor(metadata, + org.springframework.context.annotation.PropertySource.class); if (propertySource != null) { - String name = propertySource.getString("name"); - String[] locations = propertySource.getStringArray("value"); - int nLocations = locations.length; - if (nLocations == 0) { - throw new IllegalArgumentException("At least one @PropertySource(value) location is required"); - } - for (int i = 0; i < nLocations; i++) { - locations[i] = this.environment.resolveRequiredPlaceholders(locations[i]); - } - ClassLoader classLoader = this.resourceLoader.getClassLoader(); - if (!StringUtils.hasText(name)) { - for (String location : locations) { - this.propertySources.push(new ResourcePropertySource(location, classLoader)); - } - } - else { - if (nLocations == 1) { - this.propertySources.push(new ResourcePropertySource(name, locations[0], classLoader)); - } - else { - CompositePropertySource ps = new CompositePropertySource(name); - for (String location : locations) { - ps.addPropertySource(new ResourcePropertySource(location, classLoader)); - } - this.propertySources.push(ps); - } - } + processPropertySource(propertySource); } - // process any @ComponentScan annotions - AnnotationAttributes componentScan = attributesFor(metadata, ComponentScan.class); + // Process any @ComponentScan annotations + AnnotationAttributes componentScan = MetadataUtils.attributesFor(metadata, ComponentScan.class); if (componentScan != null) { - // the config class is annotated with @ComponentScan -> perform the scan immediately + // The config class is annotated with @ComponentScan -> perform the scan immediately Set scannedBeanDefinitions = this.componentScanParser.parse(componentScan, metadata.getClassName()); - // check the set of scanned definitions for any further config classes and parse recursively if necessary + // Check the set of scanned definitions for any further config classes and parse recursively if necessary for (BeanDefinitionHolder holder : scannedBeanDefinitions) { if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) { this.parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName()); @@ -227,47 +197,41 @@ protected AnnotationMetadata doProcessConfigurationClass( } } - // process any @Import annotations - Set imports = getImports(metadata.getClassName(), null, new HashSet()); - if (!CollectionUtils.isEmpty(imports)) { - processImport(configClass, imports.toArray(new String[imports.size()]), true); + // Process any @Import annotations + Set imports = new LinkedHashSet(); + Set visited = new LinkedHashSet(); + collectImports(metadata, imports, visited); + if (!imports.isEmpty()) { + processImport(configClass, metadata, imports, true); } - // process any @ImportResource annotations + // Process any @ImportResource annotations if (metadata.isAnnotated(ImportResource.class.getName())) { - AnnotationAttributes importResource = attributesFor(metadata, ImportResource.class); + AnnotationAttributes importResource = MetadataUtils.attributesFor(metadata, ImportResource.class); String[] resources = importResource.getStringArray("value"); Class readerClass = importResource.getClass("reader"); for (String resource : resources) { - configClass.addImportedResource(resource, readerClass); + String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); + configClass.addImportedResource(resolvedResource, readerClass); } } - // process individual @Bean methods + // Process individual @Bean methods Set beanMethods = metadata.getAnnotatedMethods(Bean.class.getName()); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } - // process superclass, if any + // Process superclass, if any if (metadata.hasSuperClass()) { String superclass = metadata.getSuperClassName(); - if (this.knownSuperclasses.add(superclass)) { + if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) { + this.knownSuperclasses.put(superclass, configClass); // superclass found, return its annotation metadata and recurse if (metadata instanceof StandardAnnotationMetadata) { Class clazz = ((StandardAnnotationMetadata) metadata).getIntrospectedClass(); return new StandardAnnotationMetadata(clazz.getSuperclass(), true); } - else if (superclass.startsWith("java")) { - // never load core JDK classes via ASM, in particular not java.lang.Object! - try { - return new StandardAnnotationMetadata( - this.resourceLoader.getClassLoader().loadClass(superclass), true); - } - catch (ClassNotFoundException ex) { - throw new IllegalStateException(ex); - } - } else { MetadataReader reader = this.metadataReaderFactory.getMetadataReader(superclass); return reader.getAnnotationMetadata(); @@ -275,10 +239,69 @@ else if (superclass.startsWith("java")) { } } - // no superclass, processing is complete + // No superclass -> processing is complete return null; } + /** + * Register member (nested) classes that happen to be configuration classes themselves. + * @param metadata the metadata representation of the containing class + * @throws IOException if there is any problem reading metadata from a member class + */ + private void processMemberClasses(AnnotationMetadata metadata) throws IOException { + if (metadata instanceof StandardAnnotationMetadata) { + for (Class memberClass : ((StandardAnnotationMetadata) metadata).getIntrospectedClass().getDeclaredClasses()) { + if (ConfigurationClassUtils.isConfigurationCandidate(new StandardAnnotationMetadata(memberClass))) { + processConfigurationClass(new ConfigurationClass(memberClass, true)); + } + } + } + else { + for (String memberClassName : metadata.getMemberClassNames()) { + MetadataReader reader = this.metadataReaderFactory.getMetadataReader(memberClassName); + AnnotationMetadata memberClassMetadata = reader.getAnnotationMetadata(); + if (ConfigurationClassUtils.isConfigurationCandidate(memberClassMetadata)) { + processConfigurationClass(new ConfigurationClass(reader, true)); + } + } + } + } + + /** + * Process the given @PropertySource annotation metadata. + * @param propertySource metadata for the @PropertySource annotation found + * @throws IOException if loading a property source failed + */ + private void processPropertySource(AnnotationAttributes propertySource) throws IOException { + String name = propertySource.getString("name"); + String[] locations = propertySource.getStringArray("value"); + int locationCount = locations.length; + if (locationCount == 0) { + throw new IllegalArgumentException("At least one @PropertySource(value) location is required"); + } + for (int i = 0; i < locationCount; i++) { + locations[i] = this.environment.resolveRequiredPlaceholders(locations[i]); + } + ClassLoader classLoader = this.resourceLoader.getClassLoader(); + if (!StringUtils.hasText(name)) { + for (String location : locations) { + this.propertySources.push(new ResourcePropertySource(location, classLoader)); + } + } + else { + if (locationCount == 1) { + this.propertySources.push(new ResourcePropertySource(name, locations[0], classLoader)); + } + else { + CompositePropertySource ps = new CompositePropertySource(name); + for (int i = locations.length - 1; i >= 0; i--) { + ps.addPropertySource(new ResourcePropertySource(locations[i], classLoader)); + } + this.propertySources.push(ps); + } + } + } + /** * Recursively collect all declared {@code @Import} values. Unlike most * meta-annotations it is valid to have several {@code @Import}s declared with @@ -287,78 +310,136 @@ else if (superclass.startsWith("java")) { *

For example, it is common for a {@code @Configuration} class to declare direct * {@code @Import}s in addition to meta-imports originating from an {@code @Enable} * annotation. - * @param className the class name to search - * @param imports the imports collected so far or {@code null} - * @param visited used to track visited classes to prevent infinite recursion (must not be null) - * @return a set of all {@link Import#value() import values} or {@code null} + * @param metadata the metadata representation of the class to search + * @param imports the imports collected so far + * @param visited used to track visited classes to prevent infinite recursion * @throws IOException if there is any problem reading metadata from the named class */ - private Set getImports(String className, Set imports, Set visited) throws IOException { - if (visited.add(className) && !className.startsWith("java")) { - AnnotationMetadata metadata = metadataReaderFactory.getMetadataReader(className).getAnnotationMetadata(); - for (String annotationType : metadata.getAnnotationTypes()) { - imports = getImports(annotationType, imports, visited); + private void collectImports(AnnotationMetadata metadata, Set imports, Set visited) throws IOException { + String className = metadata.getClassName(); + if (visited.add(className)) { + if (metadata instanceof StandardAnnotationMetadata) { + StandardAnnotationMetadata stdMetadata = (StandardAnnotationMetadata) metadata; + for (Annotation ann : stdMetadata.getIntrospectedClass().getAnnotations()) { + if (!ann.annotationType().getName().startsWith("java") && !(ann instanceof Import)) { + collectImports(new StandardAnnotationMetadata(ann.annotationType()), imports, visited); + } + } + Map attributes = stdMetadata.getAnnotationAttributes(Import.class.getName(), false); + if (attributes != null) { + Class[] value = (Class[]) attributes.get("value"); + if (!ObjectUtils.isEmpty(value)) { + for (Class importedClass : value) { + // Catch duplicate from ASM-based parsing... + imports.remove(importedClass.getName()); + imports.add(importedClass); + } + } + } } - Map attributes = metadata.getAnnotationAttributes(Import.class.getName(), true); - if (attributes != null) { - String[] value = (String[]) attributes.get("value"); - if (value != null && value.length > 0) { - imports = (imports == null ? new LinkedHashSet() : imports); - imports.addAll(Arrays.asList(value)); + else { + for (String annotationType : metadata.getAnnotationTypes()) { + if (!className.startsWith("java") && !className.equals(Import.class.getName())) { + try { + collectImports( + new StandardAnnotationMetadata(this.resourceLoader.getClassLoader().loadClass(annotationType)), + imports, visited); + } + catch (ClassNotFoundException ex) { + // Silently ignore... + } + } + } + Map attributes = metadata.getAnnotationAttributes(Import.class.getName(), true); + if (attributes != null) { + String[] value = (String[]) attributes.get("value"); + if (!ObjectUtils.isEmpty(value)) { + for (String importedClassName : value) { + // Catch duplicate from reflection-based parsing... + boolean alreadyThereAsClass = false; + for (Object existingImport : imports) { + if (existingImport instanceof Class && + ((Class) existingImport).getName().equals(importedClassName)) { + alreadyThereAsClass = true; + } + } + if (!alreadyThereAsClass) { + imports.add(importedClassName); + } + } + } } } } - return imports; } - private void processImport(ConfigurationClass configClass, String[] classesToImport, boolean checkForCircularImports) throws IOException { + private void processImport(ConfigurationClass configClass, AnnotationMetadata metadata, + Collection classesToImport, boolean checkForCircularImports) throws IOException { + if (checkForCircularImports && this.importStack.contains(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack, configClass.getMetadata())); } else { this.importStack.push(configClass); - AnnotationMetadata importingClassMetadata = configClass.getMetadata(); - for (String candidate : classesToImport) { - MetadataReader reader = this.metadataReaderFactory.getMetadataReader(candidate); - if (new AssignableTypeFilter(ImportSelector.class).match(reader, this.metadataReaderFactory)) { - // the candidate class is an ImportSelector -> delegate to it to determine imports - try { - ImportSelector selector = BeanUtils.instantiateClass( - this.resourceLoader.getClassLoader().loadClass(candidate), ImportSelector.class); - processImport(configClass, selector.selectImports(importingClassMetadata), false); - } - catch (ClassNotFoundException ex) { - throw new IllegalStateException(ex); + try { + for (Object candidate : classesToImport) { + Object candidateToCheck = (candidate instanceof Class ? (Class) candidate : + this.metadataReaderFactory.getMetadataReader((String) candidate)); + if (checkAssignability(ImportSelector.class, candidateToCheck)) { + // Candidate class is an ImportSelector -> delegate to it to determine imports + Class candidateClass = (candidate instanceof Class ? (Class) candidate : + this.resourceLoader.getClassLoader().loadClass((String) candidate)); + ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class); + processImport(configClass, metadata, Arrays.asList(selector.selectImports(metadata)), false); } - } - else if (new AssignableTypeFilter(ImportBeanDefinitionRegistrar.class).match(reader, this.metadataReaderFactory)) { - // the candidate class is an ImportBeanDefinitionRegistrar -> delegate to it to register additional bean definitions - try { - ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass( - this.resourceLoader.getClassLoader().loadClass(candidate), ImportBeanDefinitionRegistrar.class); + else if (checkAssignability(ImportBeanDefinitionRegistrar.class, candidateToCheck)) { + // Candidate class is an ImportBeanDefinitionRegistrar -> + // delegate to it to register additional bean definitions + Class candidateClass = (candidate instanceof Class ? (Class) candidate : + this.resourceLoader.getClassLoader().loadClass((String) candidate)); + ImportBeanDefinitionRegistrar registrar = + BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class); invokeAwareMethods(registrar); - registrar.registerBeanDefinitions(importingClassMetadata, registry); + registrar.registerBeanDefinitions(metadata, this.registry); } - catch (ClassNotFoundException ex) { - throw new IllegalStateException(ex); + else { + // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> + // process it as a @Configuration class + this.importStack.registerImport(metadata, + (candidate instanceof Class ? ((Class) candidate).getName() : (String) candidate)); + processConfigurationClass(candidateToCheck instanceof Class ? + new ConfigurationClass((Class) candidateToCheck, true) : + new ConfigurationClass((MetadataReader) candidateToCheck, true)); } } - else { - // the candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> process it as a @Configuration class - this.importStack.registerImport(importingClassMetadata.getClassName(), candidate); - processConfigurationClass(new ConfigurationClass(reader, true)); - } } - this.importStack.pop(); + catch (ClassNotFoundException ex) { + throw new NestedIOException("Failed to load import candidate class", ex); + } + finally { + this.importStack.pop(); + } + } + } + + private boolean checkAssignability(Class clazz, Object candidate) throws IOException { + if (candidate instanceof Class) { + return clazz.isAssignableFrom((Class) candidate); + } + else { + return new AssignableTypeFilter(clazz).match((MetadataReader) candidate, this.metadataReaderFactory); } } /** - * Invoke {@link ResourceLoaderAware}, {@link BeanClassLoaderAware} and - * {@link BeanFactoryAware} contracts if implemented by the given {@code registrar}. + * Invoke {@link EnvironmentAware}, {@link ResourceLoaderAware}, {@link BeanClassLoaderAware} + * and {@link BeanFactoryAware} contracts if implemented by the given {@code registrar}. */ private void invokeAwareMethods(ImportBeanDefinitionRegistrar registrar) { if (registrar instanceof Aware) { + if (registrar instanceof EnvironmentAware) { + ((EnvironmentAware) registrar).setEnvironment(this.environment); + } if (registrar instanceof ResourceLoaderAware) { ((ResourceLoaderAware) registrar).setResourceLoader(this.resourceLoader); } @@ -393,27 +474,27 @@ public Stack> getPropertySources() { return this.propertySources; } - public ImportRegistry getImportRegistry() { + ImportRegistry getImportRegistry() { return this.importStack; } interface ImportRegistry { - String getImportingClassFor(String importedClass); + AnnotationMetadata getImportingClassFor(String importedClass); } @SuppressWarnings("serial") private static class ImportStack extends Stack implements ImportRegistry { - private final Map imports = new HashMap(); + private final Map imports = new HashMap(); - public void registerImport(String importingClass, String importedClass) { + public void registerImport(AnnotationMetadata importingClass, String importedClass) { this.imports.put(importedClass, importingClass); } - public String getImportingClassFor(String importedClass) { + public AnnotationMetadata getImportingClassFor(String importedClass) { return this.imports.get(importedClass); } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 7afbc02733..dac68b997a 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanFactory; @@ -61,7 +60,6 @@ import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReaderFactory; -import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -85,7 +83,7 @@ * @since 3.0 */ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, - ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware { + Ordered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware { private static final String IMPORT_AWARE_PROCESSOR_BEAN_NAME = ConfigurationClassPostProcessor.class.getName() + ".importAwareProcessor"; @@ -130,6 +128,10 @@ protected String buildDefaultBeanName(BeanDefinition definition) { }; + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + /** * Set the {@link SourceExtractor} to use for generated bean definitions * that correspond to {@link Bean} factory methods. @@ -233,8 +235,8 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throw new IllegalStateException( "postProcessBeanFactory already called for this post-processor against " + beanFactory); } - this.factoriesPostProcessed.add((factoryId)); - if (!this.registriesPostProcessed.contains((factoryId))) { + this.factoriesPostProcessed.add(factoryId); + if (!this.registriesPostProcessed.contains(factoryId)) { // BeanDefinitionRegistryPostProcessor hook apparently not supported... // Simply call processConfigurationClasses lazily at this point then. processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory); @@ -369,43 +371,33 @@ public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFact } - private static class ImportAwareBeanPostProcessor implements PriorityOrdered, BeanFactoryAware, BeanPostProcessor { + private static class ImportAwareBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware, PriorityOrdered { private BeanFactory beanFactory; - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; } - public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) { if (bean instanceof ImportAware) { ImportRegistry importRegistry = this.beanFactory.getBean(IMPORT_REGISTRY_BEAN_NAME, ImportRegistry.class); - String importingClass = importRegistry.getImportingClassFor(bean.getClass().getSuperclass().getName()); + AnnotationMetadata importingClass = importRegistry.getImportingClassFor(bean.getClass().getSuperclass().getName()); if (importingClass != null) { - try { - AnnotationMetadata metadata = - new SimpleMetadataReaderFactory().getMetadataReader(importingClass).getAnnotationMetadata(); - ((ImportAware) bean).setImportMetadata(metadata); - } - catch (IOException ex) { - // should never occur -> at this point we know the class is present anyway - throw new IllegalStateException(ex); - } - } - else { - // no importing class was found + ((ImportAware) bean).setImportMetadata(importingClass); } } return bean; } - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + public Object postProcessAfterInitialization(Object bean, String beanName) { return bean; } - - public int getOrder() { - return Ordered.HIGHEST_PRECEDENCE; - } } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java index 645203933a..de6d7760be 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.core.Conventions; @@ -37,14 +38,14 @@ */ abstract class ConfigurationClassUtils { - private static final Log logger = LogFactory.getLog(ConfigurationClassUtils.class); - private static final String CONFIGURATION_CLASS_FULL = "full"; private static final String CONFIGURATION_CLASS_LITE = "lite"; private static final String CONFIGURATION_CLASS_ATTRIBUTE = - Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass"); + Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass"); + + private static final Log logger = LogFactory.getLog(ConfigurationClassUtils.class); /** @@ -92,23 +93,45 @@ else if (isLiteConfigurationCandidate(metadata)) { return false; } + /** + * Check the given metadata for a configuration class candidate + * (or nested component class declared within a configuration/component class). + * @param metadata the metadata of the annotated class + * @return {@code true} if the given class is to be registered as a + * reflection-detected bean definition; {@code false} otherwise + */ public static boolean isConfigurationCandidate(AnnotationMetadata metadata) { - return isFullConfigurationCandidate(metadata) || isLiteConfigurationCandidate(metadata); + return (isFullConfigurationCandidate(metadata) || isLiteConfigurationCandidate(metadata)); } + /** + * Check the given metadata for a full configuration class candidate + * (i.e. a class annotated with {@code @Configuration}). + * @param metadata the metadata of the annotated class + * @return {@code true} if the given class is to be processed as a full + * configuration class, including cross-method call interception + */ public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) { return metadata.isAnnotated(Configuration.class.getName()); } + /** + * Check the given metadata for a lite configuration class candidate + * (e.g. a class annotated with {@code @Component} or just having + * {@code @Bean methods}). + * @param metadata the metadata of the annotated class + * @return {@code true} if the given class is to be processed as a lite + * configuration class, just registering it and scanning it for {@code @Bean} methods + */ public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) { - return !metadata.isInterface() && // not an interface or an annotation - (metadata.isAnnotated(Component.class.getName()) || - metadata.hasAnnotatedMethods(Bean.class.getName())); + // Do not consider an interface or an annotation... + return (!metadata.isInterface() && ( + metadata.isAnnotated(Component.class.getName()) || metadata.hasAnnotatedMethods(Bean.class.getName()))); } - /** - * Determine whether the given bean definition indicates a full @Configuration class. + * Determine whether the given bean definition indicates a full + * {@code @Configuration} class. */ public static boolean isFullConfigurationClass(BeanDefinition beanDef) { return CONFIGURATION_CLASS_FULL.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE)); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationMethod.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationMethod.java index 624be36d0d..b359be7d64 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationMethod.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author 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 @@ public ConfigurationMethod(MethodMetadata metadata, ConfigurationClass configura this.configurationClass = configurationClass; } + public MethodMetadata getMetadata() { return this.metadata; } @@ -48,13 +49,22 @@ public Location getResourceLocation() { return new Location(this.configurationClass.getResource(), this.metadata); } + String getFullyQualifiedMethodName() { + return this.metadata.getDeclaringClassName() + "#" + this.metadata.getMethodName(); + } + + static String getShortMethodName(String fullyQualifiedMethodName) { + return fullyQualifiedMethodName.substring(fullyQualifiedMethodName.indexOf('#') + 1); + } + public void validate(ProblemReporter problemReporter) { } + @Override public String toString() { return String.format("[%s:name=%s,declaringClass=%s]", - this.getClass().getSimpleName(), this.getMetadata().getMethodName(), this.getMetadata().getDeclaringClassName()); + getClass().getSimpleName(), getMetadata().getMethodName(), getMetadata().getDeclaringClassName()); } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/FilterType.java b/spring-context/src/main/java/org/springframework/context/annotation/FilterType.java index 26bd302d8e..746424007d 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/FilterType.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/FilterType.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 the original author 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,6 @@ package org.springframework.context.annotation; -import org.springframework.core.type.filter.AssignableTypeFilter; - - /** * Enumeration of the type filters that may be used in conjunction with * {@link ComponentScan @ComponentScan}. @@ -42,12 +39,12 @@ public enum FilterType { /** * Filter candidates assignable to a given type. - * @see AssignableTypeFilter + * @see org.springframework.core.type.filter.AssignableTypeFilter */ ASSIGNABLE_TYPE, /** Filter candidates using a given custom - * {@link org.springframework.core.type.filter.TypeFilter} implementation + * {@link org.springframework.core.type.filter.TypeFilter} implementation. */ CUSTOM diff --git a/spring-context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfiguration.java b/spring-context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfiguration.java index d81821b703..50d3ae81f7 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfiguration.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2015 the original author 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,16 +26,15 @@ import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.instrument.classloading.LoadTimeWeaver; -import org.springframework.util.Assert; import static org.springframework.context.weaving.AspectJWeavingEnabler.*; /** * {@code @Configuration} class that registers a {@link LoadTimeWeaver} bean. * - *

This configuration class is automatically imported when using the @{@link - * EnableLoadTimeWeaving} annotation. See {@code @EnableLoadTimeWeaving} Javadoc for - * complete usage details. + *

This configuration class is automatically imported when using the + * {@link EnableLoadTimeWeaving} annotation. See {@code @EnableLoadTimeWeaving} + * javadoc for complete usage details. * * @author Chris Beams * @since 3.1 @@ -47,28 +46,31 @@ public class LoadTimeWeavingConfiguration implements ImportAware, BeanClassLoade private AnnotationAttributes enableLTW; - @Autowired(required=false) + @Autowired(required = false) private LoadTimeWeavingConfigurer ltwConfigurer; private ClassLoader beanClassLoader; + public void setImportMetadata(AnnotationMetadata importMetadata) { this.enableLTW = MetadataUtils.attributesFor(importMetadata, EnableLoadTimeWeaving.class); - Assert.notNull(this.enableLTW, - "@EnableLoadTimeWeaving is not present on importing class " + - importMetadata.getClassName()); + if (this.enableLTW == null) { + throw new IllegalArgumentException( + "@EnableLoadTimeWeaving is not present on importing class " + importMetadata.getClassName()); + } } public void setBeanClassLoader(ClassLoader beanClassLoader) { this.beanClassLoader = beanClassLoader; } - @Bean(name=ConfigurableApplicationContext.LOAD_TIME_WEAVER_BEAN_NAME) + + @Bean(name = ConfigurableApplicationContext.LOAD_TIME_WEAVER_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public LoadTimeWeaver loadTimeWeaver() { LoadTimeWeaver loadTimeWeaver = null; - if (ltwConfigurer != null) { + if (this.ltwConfigurer != null) { // the user has provided a custom LTW instance loadTimeWeaver = ltwConfigurer.getLoadTimeWeaver(); } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/MBeanExportConfiguration.java b/spring-context/src/main/java/org/springframework/context/annotation/MBeanExportConfiguration.java index f9d38ecefd..223729cd19 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/MBeanExportConfiguration.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/MBeanExportConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 the original author 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,29 +17,27 @@ package org.springframework.context.annotation; import java.util.Map; - import javax.management.MBeanServer; +import javax.naming.NamingException; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; +import org.springframework.jmx.MBeanServerNotFoundException; import org.springframework.jmx.export.annotation.AnnotationMBeanExporter; import org.springframework.jmx.support.RegistrationPolicy; import org.springframework.jmx.support.WebSphereMBeanServerFactoryBean; -import org.springframework.jndi.JndiObjectFactoryBean; -import org.springframework.util.Assert; +import org.springframework.jndi.JndiLocatorDelegate; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** * {@code @Configuration} class that registers a {@link AnnotationMBeanExporter} bean. * - *

This configuration class is automatically imported when using the @{@link - * EnableMBeanExport} annotation. See its Javadoc for complete usage details. + *

This configuration class is automatically imported when using the + * {@link EnableMBeanExport} annotation. See its javadoc for complete usage details. * * @author Phillip Webb * @author Chris Beams @@ -51,23 +49,26 @@ public class MBeanExportConfiguration implements ImportAware, BeanFactoryAware { private static final String MBEAN_EXPORTER_BEAN_NAME = "mbeanExporter"; - private AnnotationAttributes attributes; + private AnnotationAttributes enableMBeanExport; private BeanFactory beanFactory; public void setImportMetadata(AnnotationMetadata importMetadata) { Map map = importMetadata.getAnnotationAttributes(EnableMBeanExport.class.getName()); - this.attributes = AnnotationAttributes.fromMap(map); - Assert.notNull(this.attributes, "@EnableMBeanExport is not present on " + - "importing class " + importMetadata.getClassName()); + this.enableMBeanExport = AnnotationAttributes.fromMap(map); + if (this.enableMBeanExport == null) { + throw new IllegalArgumentException( + "@EnableMBeanExport is not present on importing class " + importMetadata.getClassName()); + } } - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; } - @Bean(name=MBEAN_EXPORTER_BEAN_NAME) + + @Bean(name = MBEAN_EXPORTER_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public AnnotationMBeanExporter mbeanExporter() { AnnotationMBeanExporter exporter = new AnnotationMBeanExporter(); @@ -78,46 +79,51 @@ public AnnotationMBeanExporter mbeanExporter() { } private void setupDomain(AnnotationMBeanExporter exporter) { - String defaultDomain = this.attributes.getString("defaultDomain"); + String defaultDomain = this.enableMBeanExport.getString("defaultDomain"); if (StringUtils.hasText(defaultDomain)) { exporter.setDefaultDomain(defaultDomain); } } private void setupServer(AnnotationMBeanExporter exporter) { - String server = this.attributes.getString("server"); + String server = this.enableMBeanExport.getString("server"); if (StringUtils.hasText(server)) { exporter.setServer(this.beanFactory.getBean(server, MBeanServer.class)); } else { SpecificPlatform specificPlatform = SpecificPlatform.get(); - if(specificPlatform != null) { + if (specificPlatform != null) { exporter.setServer(specificPlatform.getMBeanServer()); } } } private void setupRegistrationPolicy(AnnotationMBeanExporter exporter) { - RegistrationPolicy registrationPolicy = this.attributes.getEnum("registration"); + RegistrationPolicy registrationPolicy = this.enableMBeanExport.getEnum("registration"); exporter.setRegistrationPolicy(registrationPolicy); } - private static enum SpecificPlatform { + public static enum SpecificPlatform { WEBLOGIC("weblogic.management.Helper") { @Override - public FactoryBean getMBeanServerFactory() { - JndiObjectFactoryBean factory = new JndiObjectFactoryBean(); - factory.setJndiName("java:comp/env/jmx/runtime"); - return factory; + public MBeanServer getMBeanServer() { + try { + return new JndiLocatorDelegate().lookup("java:comp/env/jmx/runtime", MBeanServer.class); + } + catch (NamingException ex) { + throw new MBeanServerNotFoundException("Failed to retrieve WebLogic MBeanServer from JNDI", ex); + } } }, WEBSPHERE("com.ibm.websphere.management.AdminServiceFactory") { @Override - public FactoryBean getMBeanServerFactory() { - return new WebSphereMBeanServerFactoryBean(); + public MBeanServer getMBeanServer() { + WebSphereMBeanServerFactoryBean fb = new WebSphereMBeanServerFactoryBean(); + fb.afterPropertiesSet(); + return fb.getObject(); } }; @@ -127,27 +133,17 @@ private SpecificPlatform(String identifyingClass) { this.identifyingClass = identifyingClass; } - public MBeanServer getMBeanServer() { - Object server; - try { - server = getMBeanServerFactory().getObject(); - Assert.isInstanceOf(MBeanServer.class, server); - return (MBeanServer) server; - } catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - protected abstract FactoryBean getMBeanServerFactory(); + public abstract MBeanServer getMBeanServer(); public static SpecificPlatform get() { ClassLoader classLoader = MBeanExportConfiguration.class.getClassLoader(); for (SpecificPlatform environment : values()) { - if(ClassUtils.isPresent(environment.identifyingClass, classLoader)) { + if (ClassUtils.isPresent(environment.identifyingClass, classLoader)) { return environment; } } return null; } } + } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Profile.java b/spring-context/src/main/java/org/springframework/context/annotation/Profile.java index ab793599c0..4d9c603910 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Profile.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Profile.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.context.annotation; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -44,11 +45,11 @@ * *

If a {@code @Configuration} class is marked with {@code @Profile}, all of the * {@code @Bean} methods and {@link Import @Import} annotations associated with that class - * will be bypassed unless one or more the specified profiles are active. This is very + * will be bypassed unless one or more of the specified profiles are active. This is very * similar to the behavior in Spring XML: if the {@code profile} attribute of the * {@code beans} element is supplied e.g., {@code }, the * {@code beans} element will not be parsed unless profiles 'p1' and/or 'p2' have been - * activated. Likewise, if a {@code @Component} or {@code @Configuration} class is marked + * activated. Likewise, if a {@code @Component} or {@code @Configuration} class is marked * with {@code @Profile({"p1", "p2"})}, that class will not be registered/processed unless * profiles 'p1' and/or 'p2' have been activated. * @@ -73,10 +74,11 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) +@Documented public @interface Profile { /** - * The set of profiles for which this component should be registered. + * The set of profiles for which the annotated component should be registered. */ String[] value(); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Role.java b/spring-context/src/main/java/org/springframework/context/annotation/Role.java index 3abdca32b1..f8dc81ca6d 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Role.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Role.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2015 the original author 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,16 +28,16 @@ * Indicates the 'role' hint for a given bean. * *

May be used on any class directly or indirectly annotated with - * {@link org.springframework.stereotype.Component} or on methods annotated with - * {@link Bean}. + * {@link org.springframework.stereotype.Component} or on methods + * annotated with {@link Bean}. * - *

If this annotation is not present on a Component or Bean definition, the - * default value of {@link BeanDefinition#ROLE_APPLICATION} will apply. + *

If this annotation is not present on a Component or Bean definition, + * the default value of {@link BeanDefinition#ROLE_APPLICATION} will apply. * - *

If Role is present on a {@link Configuration @Configuration} class, this - * indicates the role of the configuration class bean definition and does not - * cascade to all @{@code Bean} methods defined within. This behavior is - * different than that of the @{@link Lazy} annotation, for example. + *

If Role is present on a {@link Configuration @Configuration} class, + * this indicates the role of the configuration class bean definition and + * does not cascade to all @{@code Bean} methods defined within. This behavior + * is different than that of the @{@link Lazy} annotation, for example. * * @author Chris Beams * @since 3.1 diff --git a/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java b/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java index 055b5e59db..2957f98f4d 100644 --- a/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java +++ b/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 the original author 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,11 +23,16 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.support.AbstractBeanFactory; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.core.OrderComparator; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; /** * Abstract implementation of the {@link ApplicationEventMulticaster} interface, @@ -48,64 +53,80 @@ * @see #getApplicationListeners(ApplicationEvent) * @see SimpleApplicationEventMulticaster */ -public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanFactoryAware { +public abstract class AbstractApplicationEventMulticaster + implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware { private final ListenerRetriever defaultRetriever = new ListenerRetriever(false); private final Map retrieverCache = new ConcurrentHashMap(64); + private ClassLoader beanClassLoader; + private BeanFactory beanFactory; + private Object retrievalMutex = this.defaultRetriever; + + + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + if (this.beanClassLoader == null && beanFactory instanceof ConfigurableBeanFactory) { + this.beanClassLoader = ((ConfigurableBeanFactory) beanFactory).getBeanClassLoader(); + } + if (beanFactory instanceof AbstractBeanFactory) { + this.retrievalMutex = ((AbstractBeanFactory) beanFactory).getSingletonMutex(); + } + } + + private BeanFactory getBeanFactory() { + if (this.beanFactory == null) { + throw new IllegalStateException("ApplicationEventMulticaster cannot retrieve listener beans " + + "because it is not associated with a BeanFactory"); + } + return this.beanFactory; + } + public void addApplicationListener(ApplicationListener listener) { - synchronized (this.defaultRetriever) { + synchronized (this.retrievalMutex) { this.defaultRetriever.applicationListeners.add(listener); this.retrieverCache.clear(); } } public void addApplicationListenerBean(String listenerBeanName) { - synchronized (this.defaultRetriever) { + synchronized (this.retrievalMutex) { this.defaultRetriever.applicationListenerBeans.add(listenerBeanName); this.retrieverCache.clear(); } } public void removeApplicationListener(ApplicationListener listener) { - synchronized (this.defaultRetriever) { + synchronized (this.retrievalMutex) { this.defaultRetriever.applicationListeners.remove(listener); this.retrieverCache.clear(); } } public void removeApplicationListenerBean(String listenerBeanName) { - synchronized (this.defaultRetriever) { + synchronized (this.retrievalMutex) { this.defaultRetriever.applicationListenerBeans.remove(listenerBeanName); this.retrieverCache.clear(); } } public void removeAllListeners() { - synchronized (this.defaultRetriever) { + synchronized (this.retrievalMutex) { this.defaultRetriever.applicationListeners.clear(); this.defaultRetriever.applicationListenerBeans.clear(); this.retrieverCache.clear(); } } - public final void setBeanFactory(BeanFactory beanFactory) { - this.beanFactory = beanFactory; - } - - private BeanFactory getBeanFactory() { - if (this.beanFactory == null) { - throw new IllegalStateException("ApplicationEventMulticaster cannot retrieve listener beans " + - "because it is not associated with a BeanFactory"); - } - return this.beanFactory; - } - /** * Return a Collection containing all ApplicationListeners. @@ -113,7 +134,7 @@ private BeanFactory getBeanFactory() { * @see org.springframework.context.ApplicationListener */ protected Collection getApplicationListeners() { - synchronized (this.defaultRetriever) { + synchronized (this.retrievalMutex) { return this.defaultRetriever.getApplicationListeners(); } } @@ -128,41 +149,77 @@ protected Collection getApplicationListeners() { */ protected Collection getApplicationListeners(ApplicationEvent event) { Class eventType = event.getClass(); - Class sourceType = event.getSource().getClass(); + Object source = event.getSource(); + Class sourceType = (source != null ? source.getClass() : null); ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType); + + // Quick check for existing entry on ConcurrentHashMap... ListenerRetriever retriever = this.retrieverCache.get(cacheKey); if (retriever != null) { return retriever.getApplicationListeners(); } - else { - retriever = new ListenerRetriever(true); - LinkedList allListeners = new LinkedList(); - Set listeners; - Set listenerBeans; - synchronized (this.defaultRetriever) { - listeners = new LinkedHashSet(this.defaultRetriever.applicationListeners); - listenerBeans = new LinkedHashSet(this.defaultRetriever.applicationListenerBeans); + + if (this.beanClassLoader == null || + (ClassUtils.isCacheSafe(eventType, this.beanClassLoader) && + (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) { + // Fully synchronized building and caching of a ListenerRetriever + synchronized (this.retrievalMutex) { + retriever = this.retrieverCache.get(cacheKey); + if (retriever != null) { + return retriever.getApplicationListeners(); + } + retriever = new ListenerRetriever(true); + Collection listeners = + retrieveApplicationListeners(eventType, sourceType, retriever); + this.retrieverCache.put(cacheKey, retriever); + return listeners; } - for (ApplicationListener listener : listeners) { - if (supportsEvent(listener, eventType, sourceType)) { + } + else { + // No ListenerRetriever caching -> no synchronization necessary + return retrieveApplicationListeners(eventType, sourceType, null); + } + } + + /** + * Actually retrieve the application listeners for the given event and source type. + * @param eventType the application event type + * @param sourceType the event source type + * @param retriever the ListenerRetriever, if supposed to populate one (for caching purposes) + * @return the pre-filtered list of application listeners for the given event and source type + */ + private Collection retrieveApplicationListeners( + Class eventType, Class sourceType, ListenerRetriever retriever) { + + LinkedList allListeners = new LinkedList(); + Set listeners; + Set listenerBeans; + synchronized (this.retrievalMutex) { + listeners = new LinkedHashSet(this.defaultRetriever.applicationListeners); + listenerBeans = new LinkedHashSet(this.defaultRetriever.applicationListenerBeans); + } + for (ApplicationListener listener : listeners) { + if (supportsEvent(listener, eventType, sourceType)) { + if (retriever != null) { retriever.applicationListeners.add(listener); - allListeners.add(listener); } + allListeners.add(listener); } - if (!listenerBeans.isEmpty()) { - BeanFactory beanFactory = getBeanFactory(); - for (String listenerBeanName : listenerBeans) { - ApplicationListener listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class); - if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) { + } + if (!listenerBeans.isEmpty()) { + BeanFactory beanFactory = getBeanFactory(); + for (String listenerBeanName : listenerBeans) { + ApplicationListener listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class); + if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) { + if (retriever != null) { retriever.applicationListenerBeans.add(listenerBeanName); - allListeners.add(listener); } + allListeners.add(listener); } } - OrderComparator.sort(allListeners); - this.retrieverCache.put(cacheKey, retriever); - return allListeners; } + OrderComparator.sort(allListeners); + return allListeners; } /** @@ -174,11 +231,11 @@ protected Collection getApplicationListeners(ApplicationEve * @param listener the target listener to check * @param eventType the event type to check against * @param sourceType the source type to check against - * @return whether the given listener should be included in the - * candidates for the given event type + * @return whether the given listener should be included in the candidates + * for the given event type */ protected boolean supportsEvent( - ApplicationListener listener, Class eventType, Class sourceType) { + ApplicationListener listener, Class eventType, Class sourceType) { SmartApplicationListener smartListener = (listener instanceof SmartApplicationListener ? (SmartApplicationListener) listener : new GenericApplicationListenerAdapter(listener)); @@ -191,11 +248,11 @@ protected boolean supportsEvent( */ private static class ListenerCacheKey { - private final Class eventType; + private final Class eventType; - private final Class sourceType; + private final Class sourceType; - public ListenerCacheKey(Class eventType, Class sourceType) { + public ListenerCacheKey(Class eventType, Class sourceType) { this.eventType = eventType; this.sourceType = sourceType; } @@ -206,12 +263,13 @@ public boolean equals(Object other) { return true; } ListenerCacheKey otherKey = (ListenerCacheKey) other; - return (this.eventType.equals(otherKey.eventType) && this.sourceType.equals(otherKey.sourceType)); + return ObjectUtils.nullSafeEquals(this.eventType, otherKey.eventType) && + ObjectUtils.nullSafeEquals(this.sourceType, otherKey.sourceType); } @Override public int hashCode() { - return this.eventType.hashCode() * 29 + this.sourceType.hashCode(); + return ObjectUtils.nullSafeHashCode(this.eventType) * 29 + ObjectUtils.nullSafeHashCode(this.sourceType); } } diff --git a/spring-context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java b/spring-context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java index 5866212e87..f70fadf98b 100644 --- a/spring-context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java +++ b/spring-context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,9 +52,9 @@ public void onApplicationEvent(ApplicationEvent event) { } public boolean supportsEventType(Class eventType) { - Class typeArg = GenericTypeResolver.resolveTypeArgument(this.delegate.getClass(), ApplicationListener.class); + Class typeArg = GenericTypeResolver.resolveTypeArgument(this.delegate.getClass(), ApplicationListener.class); if (typeArg == null || typeArg.equals(ApplicationEvent.class)) { - Class targetClass = AopUtils.getTargetClass(this.delegate); + Class targetClass = AopUtils.getTargetClass(this.delegate); if (targetClass != this.delegate.getClass()) { typeArg = GenericTypeResolver.resolveTypeArgument(targetClass, ApplicationListener.class); } diff --git a/spring-context/src/main/java/org/springframework/context/event/SourceFilteringListener.java b/spring-context/src/main/java/org/springframework/context/event/SourceFilteringListener.java index a9326c5b16..2fef497de2 100644 --- a/spring-context/src/main/java/org/springframework/context/event/SourceFilteringListener.java +++ b/spring-context/src/main/java/org/springframework/context/event/SourceFilteringListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -74,7 +74,7 @@ public boolean supportsEventType(Class eventType) { } public boolean supportsSourceType(Class sourceType) { - return sourceType.isInstance(this.source); + return (sourceType != null && sourceType.isInstance(this.source)); } public int getOrder() { diff --git a/spring-context/src/main/java/org/springframework/context/expression/BeanFactoryAccessor.java b/spring-context/src/main/java/org/springframework/context/expression/BeanFactoryAccessor.java index 486ded925f..dc0a77b643 100644 --- a/spring-context/src/main/java/org/springframework/context/expression/BeanFactoryAccessor.java +++ b/spring-context/src/main/java/org/springframework/context/expression/BeanFactoryAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2014 the original author 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,6 +32,11 @@ */ public class BeanFactoryAccessor implements PropertyAccessor { + public Class[] getSpecificTargetClasses() { + return new Class[] {BeanFactory.class}; + } + + @Override public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException { return (((BeanFactory) target).containsBean(name)); } @@ -48,8 +53,4 @@ public void write(EvaluationContext context, Object target, String name, Object throw new AccessException("Beans in a BeanFactory are read-only"); } - public Class[] getSpecificTargetClasses() { - return new Class[] {BeanFactory.class}; - } - } diff --git a/spring-context/src/main/java/org/springframework/context/expression/EnvironmentAccessor.java b/spring-context/src/main/java/org/springframework/context/expression/EnvironmentAccessor.java index d041610a5e..36be60abee 100644 --- a/spring-context/src/main/java/org/springframework/context/expression/EnvironmentAccessor.java +++ b/spring-context/src/main/java/org/springframework/context/expression/EnvironmentAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author 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,7 @@ public class EnvironmentAccessor implements PropertyAccessor { public Class[] getSpecificTargetClasses() { - return new Class[] { Environment.class }; + return new Class[] {Environment.class}; } /** @@ -48,19 +48,18 @@ public boolean canRead(EvaluationContext context, Object target, String name) th * environment. */ public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { - return new TypedValue(((Environment)target).getProperty(name)); + return new TypedValue(((Environment) target).getProperty(name)); } /** - * Read only. - * @return false + * Read-only: returns {@code false}. */ public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { return false; } /** - * Read only. No-op. + * Read-only: no-op. */ public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException { } diff --git a/spring-context/src/main/java/org/springframework/context/expression/MapAccessor.java b/spring-context/src/main/java/org/springframework/context/expression/MapAccessor.java index 155fefbbc1..a897614d6e 100644 --- a/spring-context/src/main/java/org/springframework/context/expression/MapAccessor.java +++ b/spring-context/src/main/java/org/springframework/context/expression/MapAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,10 @@ */ public class MapAccessor implements PropertyAccessor { + public Class[] getSpecificTargetClasses() { + return new Class[] {Map.class}; + } + public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException { Map map = (Map) target; return map.containsKey(name); @@ -57,10 +61,6 @@ public void write(EvaluationContext context, Object target, String name, Object map.put(name, newValue); } - public Class[] getSpecificTargetClasses() { - return new Class[] {Map.class}; - } - /** * Exception thrown from {@code read} in order to reset a cached diff --git a/spring-context/src/main/java/org/springframework/context/i18n/LocaleContextHolder.java b/spring-context/src/main/java/org/springframework/context/i18n/LocaleContextHolder.java index 2ca6ea2f85..c1d716793a 100644 --- a/spring-context/src/main/java/org/springframework/context/i18n/LocaleContextHolder.java +++ b/spring-context/src/main/java/org/springframework/context/i18n/LocaleContextHolder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,7 +59,8 @@ public static void resetLocaleContext() { /** * Associate the given LocaleContext with the current thread, * not exposing it as inheritable for child threads. - * @param localeContext the current LocaleContext + * @param localeContext the current LocaleContext, + * or {@code null} to reset the thread-bound context */ public static void setLocaleContext(LocaleContext localeContext) { setLocaleContext(localeContext, false); diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 6bfc380aa5..873e235e67 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.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. @@ -53,6 +53,7 @@ import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.context.EnvironmentAware; import org.springframework.context.HierarchicalMessageSource; import org.springframework.context.LifecycleProcessor; @@ -217,7 +218,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader * Create a new AbstractApplicationContext with no parent. */ public AbstractApplicationContext() { - this(null); + this.resourcePatternResolver = getResourcePatternResolver(); } /** @@ -225,8 +226,8 @@ public AbstractApplicationContext() { * @param parent the parent context */ public AbstractApplicationContext(ApplicationContext parent) { - this.parent = parent; - this.resourcePatternResolver = getResourcePatternResolver(); + this(); + setParent(parent); } @@ -404,8 +405,9 @@ public void setParent(ApplicationContext parent) { } } - public void addBeanFactoryPostProcessor(BeanFactoryPostProcessor beanFactoryPostProcessor) { - this.beanFactoryPostProcessors.add(beanFactoryPostProcessor); + public void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor) { + Assert.notNull(postProcessor, "BeanFactoryPostProcessor must not be null"); + this.beanFactoryPostProcessors.add(postProcessor); } @@ -418,6 +420,7 @@ public List getBeanFactoryPostProcessors() { } public void addApplicationListener(ApplicationListener listener) { + Assert.notNull(listener, "ApplicationListener must not be null"); if (this.applicationEventMulticaster != null) { this.applicationEventMulticaster.addApplicationListener(listener); } @@ -483,6 +486,11 @@ public void refresh() throws BeansException, IllegalStateException { } catch (BeansException ex) { + if (logger.isWarnEnabled()) { + logger.warn("Exception encountered during context initialization - " + + "cancelling refresh attempt: " + ex); + } + // Destroy already created singletons to avoid dangling resources. destroyBeans(); @@ -555,11 +563,12 @@ protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) { // Configure the bean factory with context callbacks. beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this)); + beanFactory.ignoreDependencyInterface(EnvironmentAware.class); + beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class); beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class); beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class); beanFactory.ignoreDependencyInterface(MessageSourceAware.class); beanFactory.ignoreDependencyInterface(ApplicationContextAware.class); - beanFactory.ignoreDependencyInterface(EnvironmentAware.class); // BeanFactory interface not registered as resolvable type in a plain factory. // MessageSource registered (and found for autowiring) as a bean. @@ -882,11 +891,12 @@ protected void registerListeners() { for (ApplicationListener listener : getApplicationListeners()) { getApplicationEventMulticaster().addApplicationListener(listener); } + // Do not initialize FactoryBeans here: We need to leave all regular beans // uninitialized to let post-processors apply to them! String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false); - for (String lisName : listenerBeanNames) { - getApplicationEventMulticaster().addApplicationListenerBean(lisName); + for (String listenerBeanName : listenerBeanNames) { + getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName); } } @@ -1285,7 +1295,7 @@ public void stop() { } public boolean isRunning() { - return getLifecycleProcessor().isRunning(); + return (this.lifecycleProcessor != null && this.lifecycleProcessor.isRunning()); } diff --git a/spring-context/src/main/java/org/springframework/context/support/GenericXmlApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/GenericXmlApplicationContext.java index e120f667fb..eef4d80ba4 100644 --- a/spring-context/src/main/java/org/springframework/context/support/GenericXmlApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/GenericXmlApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,9 @@ public class GenericXmlApplicationContext extends GenericApplicationContext { /** * Create a new GenericXmlApplicationContext that needs to be - * {@linkplain #load loaded} and then manually {@link #refresh refreshed}. + * {@link #load loaded} and then manually {@link #refresh refreshed}. */ public GenericXmlApplicationContext() { - reader.setEnvironment(this.getEnvironment()); } /** @@ -91,14 +90,13 @@ public void setValidating(boolean validating) { } /** - * {@inheritDoc} - *

Delegates the given environment to underlying {@link XmlBeanDefinitionReader}. - * Should be called before any call to {@link #load}. + * Delegates the given environment to underlying {@link XmlBeanDefinitionReader}. + * Should be called before any call to {@code #load}. */ @Override public void setEnvironment(ConfigurableEnvironment environment) { super.setEnvironment(environment); - this.reader.setEnvironment(this.getEnvironment()); + this.reader.setEnvironment(getEnvironment()); } /** diff --git a/spring-context/src/main/java/org/springframework/context/support/LiveBeansView.java b/spring-context/src/main/java/org/springframework/context/support/LiveBeansView.java index 7cdfb8d492..73863ef6c0 100644 --- a/spring-context/src/main/java/org/springframework/context/support/LiveBeansView.java +++ b/spring-context/src/main/java/org/springframework/context/support/LiveBeansView.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author 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,6 +25,7 @@ import javax.management.ObjectName; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -40,7 +41,7 @@ * (driven by the {@value #MBEAN_DOMAIN_PROPERTY_NAME} environment property). * *

Note: This feature is still in beta and primarily designed for use with - * Spring Tool Suite 3.1. + * Spring Tool Suite 3.1 and higher. * * @author Juergen Hoeller * @since 3.2 @@ -56,6 +57,7 @@ public class LiveBeansView implements LiveBeansViewMBean, ApplicationContextAwar private static final Set applicationContexts = new LinkedHashSet(); + static void registerApplicationContext(ConfigurableApplicationContext applicationContext) { String mbeanDomain = applicationContext.getEnvironment().getProperty(MBEAN_DOMAIN_PROPERTY_NAME); if (mbeanDomain != null) { @@ -93,6 +95,7 @@ static void unregisterApplicationContext(ConfigurableApplicationContext applicat private ConfigurableApplicationContext applicationContext; + public void setApplicationContext(ApplicationContext applicationContext) { Assert.isTrue(applicationContext instanceof ConfigurableApplicationContext, "ApplicationContext does not implement ConfigurableApplicationContext"); @@ -116,6 +119,17 @@ public String getSnapshotAsJson() { return generateJson(contexts); } + /** + * Find all applicable ApplicationContexts for the current application. + *

Called if no specific ApplicationContext has been set for this LiveBeansView. + * @return the set of ApplicationContexts + */ + protected Set findApplicationContexts() { + synchronized (applicationContexts) { + return new LinkedHashSet(applicationContexts); + } + } + /** * Actually generate a JSON snapshot of the beans in the given ApplicationContexts. *

This implementation doesn't use any JSON parsing libraries in order to avoid @@ -141,11 +155,13 @@ protected String generateJson(Set contexts) { result.append("\"beans\": [\n"); ConfigurableListableBeanFactory bf = context.getBeanFactory(); String[] beanNames = bf.getBeanDefinitionNames(); - for (int i = 0; i < beanNames.length; i++) { - String beanName = beanNames[i]; + boolean elementAppended = false; + for (String beanName : beanNames) { BeanDefinition bd = bf.getBeanDefinition(beanName); - if (bd.getRole() != BeanDefinition.ROLE_INFRASTRUCTURE && - (!bd.isLazyInit() || bf.containsSingleton(beanName))) { + if (isBeanEligible(beanName, bd, bf)) { + if (elementAppended) { + result.append(",\n"); + } result.append("{\n\"bean\": \"").append(beanName).append("\",\n"); String scope = bd.getScope(); if (!StringUtils.hasText(scope)) { @@ -159,8 +175,7 @@ protected String generateJson(Set contexts) { else { result.append("\"type\": null,\n"); } - String resource = StringUtils.replace(bd.getResourceDescription(), "\\", "/"); - result.append("\"resource\": \"").append(resource).append("\",\n"); + result.append("\"resource\": \"").append(getEscapedResourceDescription(bd)).append("\",\n"); result.append("\"dependencies\": ["); String[] dependencies = bf.getDependenciesForBean(beanName); if (dependencies.length > 0) { @@ -171,9 +186,7 @@ protected String generateJson(Set contexts) { result.append("\""); } result.append("]\n}"); - if (i < beanNames.length - 1) { - result.append(",\n"); - } + elementAppended = true; } } result.append("]\n"); @@ -187,14 +200,43 @@ protected String generateJson(Set contexts) { } /** - * Find all applicable ApplicationContexts for the current application. - *

Called if no specific ApplicationContext has been set for this LiveBeansView. - * @return the set of ApplicationContexts + * Determine whether the specified bean is eligible for inclusion in the + * LiveBeansView JSON snapshot. + * @param beanName the name of the bean + * @param bd the corresponding bean definition + * @param bf the containing bean factory + * @return {@code true} if the bean is to be included; {@code false} otherwise */ - protected Set findApplicationContexts() { - synchronized (applicationContexts) { - return new LinkedHashSet(applicationContexts); + protected boolean isBeanEligible(String beanName, BeanDefinition bd, ConfigurableBeanFactory bf) { + return (bd.getRole() != BeanDefinition.ROLE_INFRASTRUCTURE && + (!bd.isLazyInit() || bf.containsSingleton(beanName))); + } + + /** + * Determine a resource description for the given bean definition and + * apply basic JSON escaping (backslashes, double quotes) to it. + * @param bd the bean definition to build the resource description for + * @return the JSON-escaped resource description + */ + protected String getEscapedResourceDescription(BeanDefinition bd) { + String resourceDescription = bd.getResourceDescription(); + if (resourceDescription == null) { + return null; + } + StringBuilder result = new StringBuilder(resourceDescription.length() + 16); + for (int i = 0; i < resourceDescription.length(); i++) { + char character = resourceDescription.charAt(i); + if (character == '\\') { + result.append('/'); + } + else if (character == '"') { + result.append("\\").append('"'); + } + else { + result.append(character); + } } + return result.toString(); } } diff --git a/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java b/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java index c831bbb855..3182390402 100644 --- a/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java +++ b/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -108,8 +108,8 @@ public void setEnvironment(Environment environment) { *

Processing occurs by replacing ${...} placeholders in bean definitions by resolving each * against this configurer's set of {@link PropertySources}, which includes: *