diff --git a/build.gradle b/build.gradle index 6021fa574ddf..5144dbccf8cd 100644 --- a/build.gradle +++ b/build.gradle @@ -3,8 +3,8 @@ plugins { id 'io.spring.nohttp' version '0.0.10' id "io.freefair.aspectj" version '6.2.0' apply false id 'org.jetbrains.dokka' version '1.5.0' apply false - id 'org.jetbrains.kotlin.jvm' version '1.5.31' apply false - id "org.jetbrains.kotlin.plugin.serialization" version "1.5.31" apply false + id 'org.jetbrains.kotlin.jvm' version '1.5.32' apply false + id "org.jetbrains.kotlin.plugin.serialization" version "1.5.32" apply false id 'org.asciidoctor.jvm.convert' version '3.3.2' id 'org.asciidoctor.jvm.pdf' version '3.3.2' id "org.unbroken-dome.xjc" version '2.0.0' apply false @@ -27,19 +27,19 @@ configure(allprojects) { project -> dependencyManagement { imports { - mavenBom "com.fasterxml.jackson:jackson-bom:2.12.5" - mavenBom "io.netty:netty-bom:4.1.70.Final" - mavenBom "io.projectreactor:reactor-bom:2020.0.13" - mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR10" + mavenBom "com.fasterxml.jackson:jackson-bom:2.12.6" + mavenBom "io.netty:netty-bom:4.1.72.Final" + mavenBom "io.projectreactor:reactor-bom:2020.0.14" + mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR11" mavenBom "io.rsocket:rsocket-bom:1.1.1" mavenBom "org.eclipse.jetty:jetty-bom:9.4.44.v20210927" - mavenBom "org.jetbrains.kotlin:kotlin-bom:1.5.31" + mavenBom "org.jetbrains.kotlin:kotlin-bom:1.5.32" mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.5.2" mavenBom "org.jetbrains.kotlinx:kotlinx-serialization-bom:1.2.2" - mavenBom "org.junit:junit-bom:5.8.1" + mavenBom "org.junit:junit-bom:5.8.2" } dependencies { - dependencySet(group: 'org.apache.logging.log4j', version: '2.14.1') { + dependencySet(group: 'org.apache.logging.log4j', version: '2.16.0') { entry 'log4j-api' entry 'log4j-core' entry 'log4j-jul' @@ -67,23 +67,23 @@ configure(allprojects) { project -> dependency "io.reactivex:rxjava:1.3.8" dependency "io.reactivex:rxjava-reactive-streams:1.2.1" dependency "io.reactivex.rxjava2:rxjava:2.2.21" - dependency "io.reactivex.rxjava3:rxjava:3.1.1" - dependency "io.smallrye.reactive:mutiny:1.1.1" + dependency "io.reactivex.rxjava3:rxjava:3.1.3" + dependency "io.smallrye.reactive:mutiny:1.2.0" dependency "io.projectreactor.tools:blockhound:1.0.6.RELEASE" dependency "com.caucho:hessian:4.0.63" dependency "com.fasterxml:aalto-xml:1.3.0" - dependency("com.fasterxml.woodstox:woodstox-core:6.2.6") { + dependency("com.fasterxml.woodstox:woodstox-core:6.2.7") { exclude group: "stax", name: "stax-api" } - dependency "com.google.code.gson:gson:2.8.8" - dependency "com.google.protobuf:protobuf-java-util:3.18.0" + dependency "com.google.code.gson:gson:2.8.9" + dependency "com.google.protobuf:protobuf-java-util:3.19.1" dependency "com.googlecode.protobuf-java-format:protobuf-java-format:1.4" dependency("com.thoughtworks.xstream:xstream:1.4.18") { exclude group: "xpp3", name: "xpp3_min" exclude group: "xmlpull", name: "xmlpull" } - dependency "org.apache.johnzon:johnzon-jsonb:1.2.14" + dependency "org.apache.johnzon:johnzon-jsonb:1.2.15" dependency("org.codehaus.jettison:jettison:1.3.8") { exclude group: "stax", name: "stax-api" } @@ -92,10 +92,10 @@ configure(allprojects) { project -> entry 'jibx-run' } dependency "org.ogce:xpp3:1.1.6" - dependency "org.yaml:snakeyaml:1.29" + dependency "org.yaml:snakeyaml:1.30" dependency "com.h2database:h2:1.4.200" - dependency "com.github.ben-manes.caffeine:caffeine:2.9.2" + dependency "com.github.ben-manes.caffeine:caffeine:2.9.3" dependency "com.github.librepdf:openpdf:1.3.26" dependency "com.rometools:rome:1.16.0" dependency "commons-io:commons-io:2.5" @@ -128,43 +128,43 @@ configure(allprojects) { project -> dependency "org.webjars:webjars-locator-core:0.48" dependency "org.webjars:underscorejs:1.8.3" - dependencySet(group: 'org.apache.tomcat', version: '9.0.54') { + dependencySet(group: 'org.apache.tomcat', version: '9.0.56') { entry 'tomcat-util' entry('tomcat-websocket') { - exclude group: "org.apache.tomcat", name: "tomcat-websocket-api" exclude group: "org.apache.tomcat", name: "tomcat-servlet-api" + exclude group: "org.apache.tomcat", name: "tomcat-websocket-api" } } - dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.54') { + dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.56') { entry 'tomcat-embed-core' entry 'tomcat-embed-websocket' } - dependencySet(group: 'io.undertow', version: '2.2.12.Final') { + dependencySet(group: 'io.undertow', version: '2.2.14.Final') { entry 'undertow-core' - entry('undertow-websockets-jsr') { - exclude group: "org.jboss.spec.javax.websocket", name: "jboss-websocket-api_1.1_spec" - } entry('undertow-servlet') { exclude group: "org.jboss.spec.javax.servlet", name: "jboss-servlet-api_4.0_spec" exclude group: "org.jboss.spec.javax.annotation", name: "jboss-annotations-api_1.3_spec" } + entry('undertow-websockets-jsr') { + exclude group: "org.jboss.spec.javax.websocket", name: "jboss-websocket-api_1.1_spec" + } } - dependencySet(group: 'com.squareup.okhttp3', version: '3.14.9') { - entry 'okhttp' - entry 'mockwebserver' - } + dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.10" + dependency 'org.apache.httpcomponents.client5:httpclient5:5.1.2' + dependency 'org.apache.httpcomponents.core5:httpcore5-reactive:5.1.2' dependency("org.apache.httpcomponents:httpclient:4.5.13") { exclude group: "commons-logging", name: "commons-logging" } - dependency("org.apache.httpcomponents:httpasyncclient:4.1.4") { + dependency("org.apache.httpcomponents:httpasyncclient:4.1.5") { exclude group: "commons-logging", name: "commons-logging" } - dependency 'org.apache.httpcomponents.client5:httpclient5:5.1' - dependency 'org.apache.httpcomponents.core5:httpcore5-reactive:5.1.1' - dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.9" + dependencySet(group: 'com.squareup.okhttp3', version: '3.14.9') { + entry 'okhttp' + entry 'mockwebserver' + } - dependency "org.jruby:jruby:9.2.19.0" + dependency "org.jruby:jruby:9.2.20.1" dependency "org.python:jython-standalone:2.7.1" dependency "org.mozilla:rhino:1.7.11" @@ -198,18 +198,18 @@ configure(allprojects) { project -> exclude group: "org.hamcrest", name: "hamcrest-core" } } - dependencySet(group: 'org.mockito', version: '4.0.0') { + dependencySet(group: 'org.mockito', version: '4.1.0') { entry('mockito-core') { exclude group: "org.hamcrest", name: "hamcrest-core" } entry 'mockito-junit-jupiter' } - dependency "io.mockk:mockk:1.12.0" + dependency "io.mockk:mockk:1.12.1" - dependency("net.sourceforge.htmlunit:htmlunit:2.54.0") { + dependency("net.sourceforge.htmlunit:htmlunit:2.55.0") { exclude group: "commons-logging", name: "commons-logging" } - dependency("org.seleniumhq.selenium:htmlunit-driver:2.54.0") { + dependency("org.seleniumhq.selenium:htmlunit-driver:2.55.0") { exclude group: "commons-logging", name: "commons-logging" } dependency("org.seleniumhq.selenium:selenium-java:3.141.59") { @@ -237,7 +237,7 @@ configure(allprojects) { project -> dependency "com.ibm.websphere:uow:6.0.2.17" dependency "com.jamonapi:jamon:2.82" - dependency "joda-time:joda-time:2.10.10" + dependency "joda-time:joda-time:2.10.13" dependency "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.9" dependency "org.javamoney:moneta:1.3" @@ -340,7 +340,7 @@ configure([rootProject] + javaProjects) { project -> } checkstyle { - toolVersion = "9.0" + toolVersion = "9.2" configDirectory.set(rootProject.file("src/checkstyle")) } @@ -353,12 +353,12 @@ configure([rootProject] + javaProjects) { project -> testImplementation("io.mockk:mockk") testImplementation("org.assertj:assertj-core") // Pull in the latest JUnit 5 Launcher API to ensure proper support in IDEs. - testRuntimeOnly("org.junit.platform:junit-platform-launcher") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") testRuntimeOnly("org.junit.platform:junit-platform-suite-engine") testRuntimeOnly("org.apache.logging.log4j:log4j-core") - testRuntimeOnly("org.apache.logging.log4j:log4j-slf4j-impl") testRuntimeOnly("org.apache.logging.log4j:log4j-jul") + testRuntimeOnly("org.apache.logging.log4j:log4j-slf4j-impl") // JSR-305 only used for non-required meta-annotations compileOnly("com.google.code.findbugs:jsr305") testCompileOnly("com.google.code.findbugs:jsr305") @@ -383,9 +383,9 @@ configure([rootProject] + javaProjects) { project -> "https://hc.apache.org/httpcomponents-client-5.1.x/current/httpclient5/apidocs/", "https://projectreactor.io/docs/test/release/api/", "https://junit.org/junit4/javadoc/4.13.2/", - // Disabling linking to JUnit 5.8.1, since the `package-list` file no longer exists due to + // Disabling linking to JUnit 5.8.2, since the `package-list` file no longer exists due to // https://github.com/junit-team/junit5/commit/67ad4e545518b0ce2b0e7c96df31a669866d5003. - // "https://junit.org/junit5/docs/5.8.1/api/", + // "https://junit.org/junit5/docs/5.8.2/api/", "https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/", "https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/", "https://r2dbc.io/spec/0.8.5.RELEASE/api/" diff --git a/gradle.properties b/gradle.properties index b46dbd487055..63caba700222 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=5.3.13-SNAPSHOT +version=5.3.14 org.gradle.jvmargs=-Xmx1536M org.gradle.caching=true org.gradle.parallel=true diff --git a/settings.gradle b/settings.gradle index 811aa851d64e..0f97d1fcaa87 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,7 +6,7 @@ pluginManagement { } plugins { - id "com.gradle.enterprise" version "3.6.1" + id "com.gradle.enterprise" version "3.7.2" id "io.spring.ge.conventions" version "0.0.7" } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java index 3e8c838704c4..8692371d3a8c 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java @@ -45,9 +45,10 @@ * @see AdvisedSupport#setProxyTargetClass * @see AdvisedSupport#setInterfaces */ -@SuppressWarnings("serial") public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { + private static final long serialVersionUID = 7930414337282325166L; + @Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { 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 6c9efc49f0d7..5e1ed1603fa6 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactoryBean.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -421,11 +421,7 @@ private boolean isNamedBeanAnAdvisorOrAdvice(String beanName) { * are unaffected by such changes. */ private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException { - if (this.advisorChainInitialized) { - return; - } - - if (!ObjectUtils.isEmpty(this.interceptorNames)) { + if (!this.advisorChainInitialized && !ObjectUtils.isEmpty(this.interceptorNames)) { if (this.beanFactory == null) { throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " + "- cannot resolve interceptor names " + Arrays.asList(this.interceptorNames)); @@ -464,9 +460,9 @@ private synchronized void initializeAdvisorChain() throws AopConfigException, Be addAdvisorOnChainCreation(advice); } } - } - this.advisorChainInitialized = true; + this.advisorChainInitialized = true; + } } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceAdapter.java b/spring-aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceAdapter.java index dd557215c563..a6b09c63cbbe 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceAdapter.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,8 +25,8 @@ import org.springframework.aop.ThrowsAdvice; /** - * Adapter to enable {@link org.springframework.aop.MethodBeforeAdvice} - * to be used in the Spring AOP framework. + * Adapter to enable {@link org.springframework.aop.ThrowsAdvice} to be used + * in the Spring AOP framework. * * @author Rod Johnson * @author Juergen Hoeller diff --git a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-context.xml b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-context.xml index 64f222cdd372..6acd17a85671 100644 --- a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-context.xml +++ b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-context.xml @@ -16,9 +16,9 @@ - + - + diff --git a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-directPointcutEvents.xml b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-directPointcutEvents.xml index 852f7479377e..2f2024ada6cd 100644 --- a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-directPointcutEvents.xml +++ b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-directPointcutEvents.xml @@ -9,6 +9,6 @@ - + diff --git a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-pointcutRefEvents.xml b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-pointcutRefEvents.xml index 8300b280512d..0b12eb945e5d 100644 --- a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-pointcutRefEvents.xml +++ b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-pointcutRefEvents.xml @@ -10,6 +10,6 @@ - + diff --git a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutDuplication.xml b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutDuplication.xml index 33693b7c1c40..7c8f47124e6b 100644 --- a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutDuplication.xml +++ b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutDuplication.xml @@ -12,7 +12,7 @@ - + diff --git a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutMissing.xml b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutMissing.xml index 3d2037805e41..847f864eb114 100644 --- a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutMissing.xml +++ b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutMissing.xml @@ -12,7 +12,7 @@ - + diff --git a/spring-aspects/src/test/java/org/springframework/cache/aspectj/AspectJEnableCachingIsolatedTests.java b/spring-aspects/src/test/java/org/springframework/cache/aspectj/AspectJEnableCachingIsolatedTests.java index 7ca1037efbc4..4235f801df73 100644 --- a/spring-aspects/src/test/java/org/springframework/cache/aspectj/AspectJEnableCachingIsolatedTests.java +++ b/spring-aspects/src/test/java/org/springframework/cache/aspectj/AspectJEnableCachingIsolatedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.BeanCreationException; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; @@ -107,10 +106,7 @@ public void multipleCachingConfigurers() { try { load(MultiCacheManagerConfigurer.class, EnableCachingConfig.class); } - catch (BeanCreationException ex) { - Throwable root = ex.getRootCause(); - boolean condition = root instanceof IllegalStateException; - assertThat(condition).isTrue(); + catch (IllegalStateException ex) { assertThat(ex.getMessage().contains("implementations of CachingConfigurer")).isTrue(); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java index e9108e828f54..93c909e047b6 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 @@ -214,6 +214,7 @@ public interface BeanFactory { /** * Return a provider for the specified bean, allowing for lazy on-demand retrieval * of instances, including availability and uniqueness options. + *

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

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

Also, generics matching is strict here, as per the Java assignment rules. + * For lenient fallback matching with unchecked semantics (similar to the ´unchecked´ + * Java compiler warning), consider calling {@link #getBeanProvider(Class)} with the + * raw type as a second step if no full generic match is + * {@link ObjectProvider#getIfAvailable() available} with this variant. * @return a corresponding provider handle + * @param requiredType type the bean must match; can be a generic type declaration * @since 5.1 * @see ObjectProvider#iterator() * @see ObjectProvider#stream() diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java index 389f19c9e488..2642e72d7ae5 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -353,9 +353,31 @@ Map getBeansOfType(@Nullable Class type, boolean includeNonSin * @since 3.0 * @see #getBeanNamesForAnnotation * @see #getBeansWithAnnotation + * @see #getType(String) */ @Nullable A findAnnotationOnBean(String beanName, Class annotationType) throws NoSuchBeanDefinitionException; + /** + * Find an {@link Annotation} of {@code annotationType} on the specified bean, + * traversing its interfaces and super classes if no annotation can be found on + * the given class itself, as well as checking the bean's factory method (if any). + * @param beanName the name of the bean to look for annotations on + * @param annotationType the type of annotation to look for + * (at class, interface or factory method level of the specified bean) + * @param allowFactoryBeanInit whether a {@code FactoryBean} may get initialized + * just for the purpose of determining its object type + * @return the annotation of the given type if found, or {@code null} otherwise + * @throws NoSuchBeanDefinitionException if there is no bean with the given name + * @since 5.3.14 + * @see #getBeanNamesForAnnotation + * @see #getBeansWithAnnotation + * @see #getType(String, boolean) + */ + @Nullable + A findAnnotationOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException; + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java index c4e21122ed81..03e192e72e4c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,9 +34,7 @@ * *

NOTE: This interface is a special purpose interface, mainly for * internal use within the framework. It is recommended to implement the plain - * {@link BeanPostProcessor} interface as far as possible, or to derive from - * {@link InstantiationAwareBeanPostProcessorAdapter} in order to be shielded - * from extensions to this interface. + * {@link BeanPostProcessor} interface as far as possible. * * @author Juergen Hoeller * @author Rod Johnson diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 048612fbed73..b019ef5361f3 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -730,14 +730,23 @@ public Map getBeansWithAnnotation(Class an public A findAnnotationOnBean(String beanName, Class annotationType) throws NoSuchBeanDefinitionException { - return findMergedAnnotationOnBean(beanName, annotationType) + return findAnnotationOnBean(beanName, annotationType, true); + } + + @Override + @Nullable + public A findAnnotationOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException { + + return findMergedAnnotationOnBean(beanName, annotationType, allowFactoryBeanInit) .synthesize(MergedAnnotation::isPresent).orElse(null); } private MergedAnnotation findMergedAnnotationOnBean( - String beanName, Class annotationType) { + String beanName, Class annotationType, boolean allowFactoryBeanInit) { - Class beanType = getType(beanName); + Class beanType = getType(beanName, allowFactoryBeanInit); if (beanType != null) { MergedAnnotation annotation = MergedAnnotations.from(beanType, SearchStrategy.TYPE_HIERARCHY).get(annotationType); 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 a5430120dfdb..1105ead3e12f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -459,7 +459,16 @@ public Map getBeansWithAnnotation(Class an public A findAnnotationOnBean(String beanName, Class annotationType) throws NoSuchBeanDefinitionException { - Class beanType = getType(beanName); + return findAnnotationOnBean(beanName, annotationType, true); + } + + @Override + @Nullable + public A findAnnotationOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException { + + Class beanType = getType(beanName, allowFactoryBeanInit); return (beanType != null ? AnnotatedElementUtils.findMergedAnnotation(beanType, annotationType) : null); } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/AbstractJCacheConfiguration.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/AbstractJCacheConfiguration.java index 1cd5ce612f30..4d2af4b9461b 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/AbstractJCacheConfiguration.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/AbstractJCacheConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.cache.annotation.AbstractCachingConfiguration; -import org.springframework.cache.annotation.CachingConfigurer; import org.springframework.cache.interceptor.CacheResolver; import org.springframework.cache.jcache.interceptor.DefaultJCacheOperationSource; import org.springframework.cache.jcache.interceptor.JCacheOperationSource; @@ -46,11 +45,14 @@ public abstract class AbstractJCacheConfiguration extends AbstractCachingConfigu @Override - protected void useCachingConfigurer(CachingConfigurer config) { - super.useCachingConfigurer(config); - if (config instanceof JCacheConfigurer) { - this.exceptionCacheResolver = ((JCacheConfigurer) config)::exceptionCacheResolver; - } + protected void useCachingConfigurer(CachingConfigurerSupplier cachingConfigurerSupplier) { + super.useCachingConfigurer(cachingConfigurerSupplier); + this.exceptionCacheResolver = cachingConfigurerSupplier.adapt(config -> { + if (config instanceof JCacheConfigurer) { + return ((JCacheConfigurer) config).exceptionCacheResolver(); + } + return null; + }); } @Bean(name = "jCacheOperationSource") diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/JCacheConfigurer.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/JCacheConfigurer.java index 989e720aeb99..039729a02d50 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/JCacheConfigurer.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/JCacheConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,8 +26,7 @@ *

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

See {@link org.springframework.cache.annotation.EnableCaching} for * general examples and context; see {@link #exceptionCacheResolver()} for @@ -36,7 +35,6 @@ * @author Stephane Nicoll * @since 4.1 * @see CachingConfigurer - * @see JCacheConfigurerSupport * @see org.springframework.cache.annotation.EnableCaching */ public interface JCacheConfigurer extends CachingConfigurer { @@ -60,6 +58,8 @@ public interface JCacheConfigurer extends CachingConfigurer { * See {@link org.springframework.cache.annotation.EnableCaching} for more complete examples. */ @Nullable - CacheResolver exceptionCacheResolver(); + default CacheResolver exceptionCacheResolver() { + return null; + } } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java index 3edf2a2c153a..8b20e4b14822 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,7 +79,7 @@ public JCacheOperation getCacheOperation(Method method, @Nullable Class ta @Nullable private JCacheOperation computeCacheOperation(Method method, @Nullable Class targetClass) { - // Don't allow no-public methods as required. + // Don't allow non-public methods, as configured. if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; } diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java index e0b12f443f91..f6042bee9347 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,8 @@ * Subclass of Quartz's {@link JobStoreCMT} class that delegates to a Spring-managed * {@link DataSource} instead of using a Quartz-managed JDBC connection pool. * This JobStore will be used if SchedulerFactoryBean's "dataSource" property is set. + * You may also configure it explicitly, possibly as a custom subclass of this + * {@code LocalDataSourceJobStore} or as an equivalent {@code JobStoreCMT} variant. * *

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

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

A Spring-specific subclass of Quartz' JobStoreCMT will be used. * It is therefore strongly recommended to perform all operations on * the Scheduler within Spring-managed (or plain JTA) transactions. diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCacheAnnotationTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCacheAnnotationTests.java index 188cc293c974..e73dd79a222d 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCacheAnnotationTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCacheAnnotationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.CachingConfigurer; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.cache.interceptor.SimpleKeyGenerator; @@ -104,7 +104,7 @@ public void testEvictAllEarlyWithTransaction() { @Configuration @EnableCaching - static class EnableCachingConfig extends CachingConfigurerSupport { + static class EnableCachingConfig implements CachingConfigurer { @Autowired CachingProvider cachingProvider; diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheJavaConfigTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheJavaConfigTests.java index 5c12aceac022..1dcc12540d1e 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheJavaConfigTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheJavaConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -180,7 +180,7 @@ public CacheResolver exceptionCacheResolver() { @Configuration @EnableCaching - public static class EmptyConfigSupportConfig extends JCacheConfigurerSupport { + public static class EmptyConfigSupportConfig implements JCacheConfigurer { @Bean public CacheManager cm() { return new NoOpCacheManager(); @@ -190,7 +190,7 @@ public CacheManager cm() { @Configuration @EnableCaching - static class FullCachingConfigSupport extends JCacheConfigurerSupport { + static class FullCachingConfigSupport implements JCacheConfigurer { @Override @Bean @@ -220,7 +220,7 @@ public CacheResolver exceptionCacheResolver() { @Configuration @EnableCaching - static class NoExceptionCacheResolverConfig extends JCacheConfigurerSupport { + static class NoExceptionCacheResolverConfig implements JCacheConfigurer { @Override @Bean diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheErrorHandlerTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheErrorHandlerTests.java index 3b341a0c4df0..561c0af54d1b 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheErrorHandlerTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheErrorHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.interceptor.CacheErrorHandler; import org.springframework.cache.interceptor.SimpleKeyGenerator; -import org.springframework.cache.jcache.config.JCacheConfigurerSupport; +import org.springframework.cache.jcache.config.JCacheConfigurer; import org.springframework.cache.support.SimpleCacheManager; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; @@ -141,7 +141,7 @@ public void clearFail() { @Configuration @EnableCaching - static class Config extends JCacheConfigurerSupport { + static class Config implements JCacheConfigurer { @Bean @Override diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheKeyGeneratorTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheKeyGeneratorTests.java index dd5497764b0c..35db912df971 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheKeyGeneratorTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheKeyGeneratorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.cache.interceptor.SimpleKey; import org.springframework.cache.interceptor.SimpleKeyGenerator; -import org.springframework.cache.jcache.config.JCacheConfigurerSupport; +import org.springframework.cache.jcache.config.JCacheConfigurer; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -97,7 +97,7 @@ public void getFiltered() { @Configuration @EnableCaching - static class Config extends JCacheConfigurerSupport { + static class Config implements JCacheConfigurer { @Bean @Override @@ -151,7 +151,7 @@ private void expect(Object... params) { @Override public Object generate(Object target, Method method, Object... params) { assertThat(Arrays.equals(expectedParams, params)).as("Unexpected parameters: expected: " - + Arrays.toString(this.expectedParams) + " but got: " + Arrays.toString(params)).isTrue(); + + Arrays.toString(this.expectedParams) + " but got: " + Arrays.toString(params)).isTrue(); return new SimpleKey(params); } } diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java b/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java index c49d8d20c6e0..d6473e28e67b 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,12 @@ package org.springframework.cache.annotation; -import java.util.Collection; +import java.util.List; +import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; import org.springframework.cache.interceptor.CacheErrorHandler; @@ -30,6 +33,7 @@ import org.springframework.core.type.AnnotationMetadata; import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; +import org.springframework.util.function.SingletonSupplier; /** * Abstract base {@code @Configuration} class providing common structure @@ -63,36 +67,67 @@ public abstract class AbstractCachingConfiguration implements ImportAware { @Override public void setImportMetadata(AnnotationMetadata importMetadata) { this.enableCaching = AnnotationAttributes.fromMap( - importMetadata.getAnnotationAttributes(EnableCaching.class.getName(), false)); + importMetadata.getAnnotationAttributes(EnableCaching.class.getName())); if (this.enableCaching == null) { throw new IllegalArgumentException( "@EnableCaching is not present on importing class " + importMetadata.getClassName()); } } - @Autowired(required = false) - void setConfigurers(Collection configurers) { - if (CollectionUtils.isEmpty(configurers)) { - return; - } - if (configurers.size() > 1) { - throw new IllegalStateException(configurers.size() + " implementations of " + - "CachingConfigurer were found when only 1 was expected. " + - "Refactor the configuration such that CachingConfigurer is " + - "implemented only once or not at all."); - } - CachingConfigurer configurer = configurers.iterator().next(); - useCachingConfigurer(configurer); + @Autowired + void setConfigurers(ObjectProvider configurers) { + Supplier configurer = () -> { + List candidates = configurers.stream().collect(Collectors.toList()); + if (CollectionUtils.isEmpty(candidates)) { + return null; + } + if (candidates.size() > 1) { + throw new IllegalStateException(candidates.size() + " implementations of " + + "CachingConfigurer were found when only 1 was expected. " + + "Refactor the configuration such that CachingConfigurer is " + + "implemented only once or not at all."); + } + return candidates.get(0); + }; + useCachingConfigurer(new CachingConfigurerSupplier(configurer)); } /** * Extract the configuration from the nominated {@link CachingConfigurer}. */ - protected void useCachingConfigurer(CachingConfigurer config) { - this.cacheManager = config::cacheManager; - this.cacheResolver = config::cacheResolver; - this.keyGenerator = config::keyGenerator; - this.errorHandler = config::errorHandler; + protected void useCachingConfigurer(CachingConfigurerSupplier cachingConfigurerSupplier) { + this.cacheManager = cachingConfigurerSupplier.adapt(CachingConfigurer::cacheManager); + this.cacheResolver = cachingConfigurerSupplier.adapt(CachingConfigurer::cacheResolver); + this.keyGenerator = cachingConfigurerSupplier.adapt(CachingConfigurer::keyGenerator); + this.errorHandler = cachingConfigurerSupplier.adapt(CachingConfigurer::errorHandler); + } + + + protected static class CachingConfigurerSupplier { + + private final Supplier supplier; + + public CachingConfigurerSupplier(Supplier supplier) { + this.supplier = SingletonSupplier.of(supplier); + } + + /** + * Adapt the {@link CachingConfigurer} supplier to another supplier + * provided by the specified mapping function. If the underlying + * {@link CachingConfigurer} is {@code null}, {@code null} is returned + * and the mapping function is not invoked. + * @param provider the provider to use to adapt the supplier + * @param the type of the supplier + * @return another supplier mapped by the specified function + */ + @Nullable + public Supplier adapt(Function provider) { + return () -> { + CachingConfigurer cachingConfigurer = this.supplier.get(); + return (cachingConfigurer != null ? provider.apply(cachingConfigurer) : null); + }; + } + } } diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurer.java b/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurer.java index b7a609e9c238..e3dbe023f189 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurer.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,8 +26,7 @@ * Interface to be implemented by @{@link org.springframework.context.annotation.Configuration * Configuration} classes annotated with @{@link EnableCaching} that wish or need to * specify explicitly how caches are resolved and how keys are generated for annotation-driven - * cache management. Consider extending {@link CachingConfigurerSupport}, which provides a - * stub implementation of all interface methods. + * cache management. * *

See @{@link EnableCaching} for general examples and context; see * {@link #cacheManager()}, {@link #cacheResolver()} and {@link #keyGenerator()} @@ -37,7 +36,6 @@ * @author Stephane Nicoll * @since 3.1 * @see EnableCaching - * @see CachingConfigurerSupport */ public interface CachingConfigurer { @@ -64,7 +62,9 @@ public interface CachingConfigurer { * See @{@link EnableCaching} for more complete examples. */ @Nullable - CacheManager cacheManager(); + default CacheManager cacheManager() { + return null; + } /** * Return the {@link CacheResolver} bean to use to resolve regular caches for @@ -89,7 +89,9 @@ public interface CachingConfigurer { * See {@link EnableCaching} for more complete examples. */ @Nullable - CacheResolver cacheResolver(); + default CacheResolver cacheResolver() { + return null; + } /** * Return the key generator bean to use for annotation-driven cache management. @@ -110,7 +112,9 @@ public interface CachingConfigurer { * See @{@link EnableCaching} for more complete examples. */ @Nullable - KeyGenerator keyGenerator(); + default KeyGenerator keyGenerator() { + return null; + } /** * Return the {@link CacheErrorHandler} to use to handle cache-related errors. @@ -133,6 +137,8 @@ public interface CachingConfigurer { * See @{@link EnableCaching} for more complete examples. */ @Nullable - CacheErrorHandler errorHandler(); + default CacheErrorHandler errorHandler() { + return null; + } } 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 8de528875fb2..d20993ae27a7 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-2018 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -124,7 +124,7 @@ protected Object getCacheKey(Method method, @Nullable Class targetClass) { @Nullable private Collection computeCacheOperations(Method method, @Nullable Class targetClass) { - // Don't allow no-public methods as required. + // Don't allow non-public methods, as configured. if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java index 4beee8680642..688087c8e770 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-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,9 +47,10 @@ * *

If the annotation's value doesn't indicate a bean name, an appropriate * name will be built based on the short name of the class (with the first - * letter lower-cased). For example: + * letter lower-cased), unless the two first letters are uppercase. For example: * *

com.xyz.FooServiceImpl -> fooServiceImpl
+ *
com.xyz.URLFooServiceImpl -> URLFooServiceImpl
* * @author Juergen Hoeller * @author Mark Fisher 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 91b1719b7083..0c6e55670953 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-2018 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -282,7 +282,7 @@ static AnnotationAttributes attributesFor(AnnotatedTypeMetadata metadata, Class< @Nullable static AnnotationAttributes attributesFor(AnnotatedTypeMetadata metadata, String annotationClassName) { - return AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(annotationClassName, false)); + return AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(annotationClassName)); } static Set attributesForRepeatable(AnnotationMetadata metadata, @@ -298,10 +298,10 @@ static Set attributesForRepeatable( Set result = new LinkedHashSet<>(); // Direct annotation present? - addAttributesIfNotNull(result, metadata.getAnnotationAttributes(annotationClassName, false)); + addAttributesIfNotNull(result, metadata.getAnnotationAttributes(annotationClassName)); // Container annotation present? - Map container = metadata.getAnnotationAttributes(containerClassName, false); + Map container = metadata.getAnnotationAttributes(containerClassName); if (container != null && container.containsKey("value")) { for (Map containedAttributes : (Map[]) container.get("value")) { addAttributesIfNotNull(result, containedAttributes); 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 aca706e82c1f..38243b17944b 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 @@ -1331,6 +1331,16 @@ public
A findAnnotationOnBean(String beanName, Class a return getBeanFactory().findAnnotationOnBean(beanName, annotationType); } + @Override + @Nullable + public A findAnnotationOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException { + + assertBeanFactoryActive(); + return getBeanFactory().findAnnotationOnBean(beanName, annotationType, allowFactoryBeanInit); + } + //--------------------------------------------------------------------- // Implementation of HierarchicalBeanFactory interface diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AbstractAsyncConfiguration.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AbstractAsyncConfiguration.java index 60955d50077a..53d7749982cf 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AbstractAsyncConfiguration.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AbstractAsyncConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,14 @@ package org.springframework.scheduling.annotation; -import java.util.Collection; +import java.util.List; import java.util.concurrent.Executor; +import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportAware; @@ -28,6 +31,7 @@ import org.springframework.core.type.AnnotationMetadata; import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; +import org.springframework.util.function.SingletonSupplier; /** * Abstract base {@code Configuration} class providing common structure for enabling @@ -55,7 +59,7 @@ public abstract class AbstractAsyncConfiguration implements ImportAware { @Override public void setImportMetadata(AnnotationMetadata importMetadata) { this.enableAsync = AnnotationAttributes.fromMap( - importMetadata.getAnnotationAttributes(EnableAsync.class.getName(), false)); + importMetadata.getAnnotationAttributes(EnableAsync.class.getName())); if (this.enableAsync == null) { throw new IllegalArgumentException( "@EnableAsync is not present on importing class " + importMetadata.getClassName()); @@ -65,17 +69,27 @@ public void setImportMetadata(AnnotationMetadata importMetadata) { /** * Collect any {@link AsyncConfigurer} beans through autowiring. */ - @Autowired(required = false) - void setConfigurers(Collection configurers) { - if (CollectionUtils.isEmpty(configurers)) { - return; - } - if (configurers.size() > 1) { - throw new IllegalStateException("Only one AsyncConfigurer may exist"); - } - AsyncConfigurer configurer = configurers.iterator().next(); - this.executor = configurer::getAsyncExecutor; - this.exceptionHandler = configurer::getAsyncUncaughtExceptionHandler; + @Autowired + void setConfigurers(ObjectProvider configurers) { + Supplier configurer = SingletonSupplier.of(() -> { + List candidates = configurers.stream().collect(Collectors.toList()); + if (CollectionUtils.isEmpty(candidates)) { + return null; + } + if (candidates.size() > 1) { + throw new IllegalStateException("Only one AsyncConfigurer may exist"); + } + return candidates.get(0); + }); + this.executor = adapt(configurer, AsyncConfigurer::getAsyncExecutor); + this.exceptionHandler = adapt(configurer, AsyncConfigurer::getAsyncUncaughtExceptionHandler); + } + + private Supplier adapt(Supplier supplier, Function provider) { + return () -> { + AsyncConfigurer configurer = supplier.get(); + return (configurer != null ? provider.apply(configurer) : null); + }; } } diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncConfigurer.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncConfigurer.java index eab9744cf340..488297171ba6 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncConfigurer.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,11 +28,6 @@ * {@link AsyncUncaughtExceptionHandler} instance used to process exception thrown from * async method with {@code void} return type. * - *

Consider using {@link AsyncConfigurerSupport} providing default implementations for - * both methods if only one element needs to be customized. Furthermore, backward compatibility - * of this interface will be insured in case new customization options are introduced - * in the future. - * *

See @{@link EnableAsync} for usage examples. * * @author Chris Beams @@ -40,7 +35,6 @@ * @since 3.1 * @see AbstractAsyncConfiguration * @see EnableAsync - * @see AsyncConfigurerSupport */ public interface AsyncConfigurer { diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBean.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBean.java index e880489f80af..3a853297b397 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBean.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,9 +26,7 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.InitializingBean; import org.springframework.lang.Nullable; /** @@ -65,7 +63,7 @@ */ @SuppressWarnings("serial") public class ThreadPoolExecutorFactoryBean extends ExecutorConfigurationSupport - implements FactoryBean, InitializingBean, DisposableBean { + implements FactoryBean { private int corePoolSize = 1; @@ -75,6 +73,8 @@ public class ThreadPoolExecutorFactoryBean extends ExecutorConfigurationSupport private boolean allowCoreThreadTimeOut = false; + private boolean prestartAllCoreThreads = false; + private int queueCapacity = Integer.MAX_VALUE; private boolean exposeUnconfigurableExecutor = false; @@ -118,6 +118,16 @@ public void setAllowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) { this.allowCoreThreadTimeOut = allowCoreThreadTimeOut; } + /** + * Specify whether to start all core threads, causing them to idly wait for work. + *

Default is "false". + * @since 5.3.14 + * @see java.util.concurrent.ThreadPoolExecutor#prestartAllCoreThreads + */ + public void setPrestartAllCoreThreads(boolean prestartAllCoreThreads) { + this.prestartAllCoreThreads = prestartAllCoreThreads; + } + /** * Set the capacity for the ThreadPoolExecutor's BlockingQueue. * Default is {@code Integer.MAX_VALUE}. @@ -153,6 +163,9 @@ protected ExecutorService initializeExecutor( if (this.allowCoreThreadTimeOut) { executor.allowCoreThreadTimeOut(true); } + if (this.prestartAllCoreThreads) { + executor.prestartAllCoreThreads(); + } // Wrap executor with an unconfigurable decorator. this.exposedExecutor = (this.exposeUnconfigurableExecutor ? diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java index 486e0c7cddb6..928814524c67 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -94,6 +94,8 @@ public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport private boolean allowCoreThreadTimeOut = false; + private boolean prestartAllCoreThreads = false; + @Nullable private TaskDecorator taskDecorator; @@ -197,6 +199,16 @@ public void setAllowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) { this.allowCoreThreadTimeOut = allowCoreThreadTimeOut; } + /** + * Specify whether to start all core threads, causing them to idly wait for work. + *

Default is "false". + * @since 5.3.14 + * @see java.util.concurrent.ThreadPoolExecutor#prestartAllCoreThreads + */ + public void setPrestartAllCoreThreads(boolean prestartAllCoreThreads) { + this.prestartAllCoreThreads = prestartAllCoreThreads; + } + /** * Specify a custom {@link TaskDecorator} to be applied to any {@link Runnable} * about to be executed. @@ -256,6 +268,9 @@ public void execute(Runnable command) { if (this.allowCoreThreadTimeOut) { executor.allowCoreThreadTimeOut(true); } + if (this.prestartAllCoreThreads) { + executor.prestartAllCoreThreads(); + } this.threadPoolExecutor = executor; return executor; diff --git a/spring-context/src/main/java/org/springframework/validation/DataBinder.java b/spring-context/src/main/java/org/springframework/validation/DataBinder.java index 1818b94b48f2..612dfc5622a2 100644 --- a/spring-context/src/main/java/org/springframework/validation/DataBinder.java +++ b/spring-context/src/main/java/org/springframework/validation/DataBinder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -418,11 +418,13 @@ public boolean isIgnoreInvalidFields() { } /** - * Register fields that should be allowed for binding. Default is all - * fields. Restrict this for example to avoid unwanted modifications - * by malicious users when binding HTTP request parameters. - *

Supports "xxx*", "*xxx" and "*xxx*" patterns. More sophisticated matching - * can be implemented by overriding the {@code isAllowed} method. + * Register fields that should be allowed for binding. Default is all fields. + * Restrict this for example to avoid unwanted modifications by malicious + * users when binding HTTP request parameters. + *

Supports "xxx*", "*xxx", "*xxx*" and "xxx*yyy" matches (with an + * arbitrary number of pattern parts), as well as direct equality. More + * sophisticated matching can be implemented by overriding the + * {@code isAllowed} method. *

Alternatively, specify a list of disallowed fields. * @param allowedFields array of field names * @see #setDisallowedFields @@ -442,11 +444,13 @@ public String[] getAllowedFields() { } /** - * Register fields that should not be allowed for binding. Default is none. - * Mark fields as disallowed for example to avoid unwanted modifications - * by malicious users when binding HTTP request parameters. - *

Supports "xxx*", "*xxx" and "*xxx*" patterns. More sophisticated matching - * can be implemented by overriding the {@code isAllowed} method. + * Register fields that should not be allowed for binding. Default + * is none. Mark fields as disallowed for example to avoid unwanted + * modifications by malicious users when binding HTTP request parameters. + *

Supports "xxx*", "*xxx", "*xxx*" and "xxx*yyy" matches (with an + * arbitrary number of pattern parts), as well as direct equality. + * More sophisticated matching can be implemented by overriding the + * {@code isAllowed} method. *

Alternatively, specify a list of allowed fields. * @param disallowedFields array of field names * @see #setAllowedFields @@ -772,10 +776,11 @@ protected void checkAllowedFields(MutablePropertyValues mpvs) { /** * Return if the given field is allowed for binding. * Invoked for each passed-in property value. - *

The default implementation checks for "xxx*", "*xxx" and "*xxx*" matches, - * as well as direct equality, in the specified lists of allowed fields and - * disallowed fields. A field matching a disallowed pattern will not be accepted - * even if it also happens to match a pattern in the allowed list. + *

The default implementation checks for "xxx*", "*xxx", "*xxx*" and "xxx*yyy" + * matches (with an arbitrary number of pattern parts), as well as direct equality, + * in the specified lists of allowed fields and disallowed fields. A field matching + * a disallowed pattern will not be accepted even if it also happens to match a + * pattern in the allowed list. *

Can be overridden in subclasses. * @param field the field to check * @return if the field is allowed diff --git a/spring-context/src/test/java/org/springframework/aop/framework/ProxyFactoryBeanTests.java b/spring-context/src/test/java/org/springframework/aop/framework/ProxyFactoryBeanTests.java index 57df369b68eb..91d85d35dadd 100644 --- a/spring-context/src/test/java/org/springframework/aop/framework/ProxyFactoryBeanTests.java +++ b/spring-context/src/test/java/org/springframework/aop/framework/ProxyFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,10 +65,10 @@ import static org.assertj.core.api.Assertions.assertThatIOException; /** - * @since 13.03.2003 * @author Rod Johnson * @author Juergen Hoeller * @author Chris Beams + * @since 13.03.2003 */ public class ProxyFactoryBeanTests { @@ -633,20 +633,50 @@ public void testFrozenFactoryBean() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource(FROZEN_CONTEXT, CLASS)); - Advised advised = (Advised)bf.getBean("frozen"); + Advised advised = (Advised) bf.getBean("frozen"); assertThat(advised.isFrozen()).as("The proxy should be frozen").isTrue(); } @Test - public void testDetectsInterfaces() throws Exception { + public void testDetectsInterfaces() { ProxyFactoryBean fb = new ProxyFactoryBean(); fb.setTarget(new TestBean()); fb.addAdvice(new DebugInterceptor()); fb.setBeanFactory(new DefaultListableBeanFactory()); + ITestBean proxy = (ITestBean) fb.getObject(); assertThat(AopUtils.isJdkDynamicProxy(proxy)).isTrue(); } + @Test + public void testWithInterceptorNames() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerSingleton("debug", new DebugInterceptor()); + + ProxyFactoryBean fb = new ProxyFactoryBean(); + fb.setTarget(new TestBean()); + fb.setInterceptorNames("debug"); + fb.setBeanFactory(bf); + + Advised proxy = (Advised) fb.getObject(); + assertThat(proxy.getAdvisorCount()).isEqualTo(1); + } + + @Test + public void testWithLateInterceptorNames() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerSingleton("debug", new DebugInterceptor()); + + ProxyFactoryBean fb = new ProxyFactoryBean(); + fb.setTarget(new TestBean()); + fb.setBeanFactory(bf); + fb.getObject(); + + fb.setInterceptorNames("debug"); + Advised proxy = (Advised) fb.getObject(); + assertThat(proxy.getAdvisorCount()).isEqualTo(1); + } + /** * Fires only on void methods. Saves list of methods intercepted. diff --git a/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java b/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java index 4c81623f507e..b08c6f895a06 100644 --- a/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java +++ b/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; -import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.CachingConfigurer; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.concurrent.ConcurrentMapCache; import org.springframework.cache.concurrent.ConcurrentMapCacheManager; @@ -306,7 +306,7 @@ public Object getNeverCache(String key) { @Configuration @EnableCaching - public static class Spr13081Config extends CachingConfigurerSupport { + public static class Spr13081Config implements CachingConfigurer { @Bean @Override diff --git a/spring-context/src/test/java/org/springframework/cache/config/EnableCachingIntegrationTests.java b/spring-context/src/test/java/org/springframework/cache/config/EnableCachingIntegrationTests.java index 858d9c0d5b92..6a7e1127aecd 100644 --- a/spring-context/src/test/java/org/springframework/cache/config/EnableCachingIntegrationTests.java +++ b/spring-context/src/test/java/org/springframework/cache/config/EnableCachingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.Cacheable; -import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.CachingConfigurer; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -83,6 +83,19 @@ private void fooGetSimple(FooService service) { assertCacheHit(key, value, cache); } + @Test + public void barServiceWithCacheableInterfaceCglib() { + this.context = new AnnotationConfigApplicationContext(BarConfigCglib.class); + BarService service = this.context.getBean(BarService.class); + Cache cache = getCache(); + + Object key = new Object(); + assertCacheMiss(key, cache); + + Object value = service.getSimple(key); + assertCacheHit(key, value, cache); + } + @Test public void beanConditionOff() { this.context = new AnnotationConfigApplicationContext(BeanConditionConfig.class); @@ -124,7 +137,7 @@ private Cache getCache() { @Configuration - static class SharedConfig extends CachingConfigurerSupport { + static class SharedConfig implements CachingConfigurer { @Override @Bean @@ -185,6 +198,36 @@ public Object getWithCondition(Object key) { } + @Configuration + @Import(SharedConfig.class) + @EnableCaching(proxyTargetClass = true) + static class BarConfigCglib { + + @Bean + public BarService barService() { + return new BarServiceImpl(); + } + } + + + interface BarService { + + @Cacheable(cacheNames = "testCache") + Object getSimple(Object key); + } + + + static class BarServiceImpl implements BarService { + + private final AtomicLong counter = new AtomicLong(); + + @Override + public Object getSimple(Object key) { + return this.counter.getAndIncrement(); + } + } + + @Configuration @Import(FooConfig.class) @EnableCaching diff --git a/spring-context/src/test/java/org/springframework/cache/config/EnableCachingTests.java b/spring-context/src/test/java/org/springframework/cache/config/EnableCachingTests.java index ea7717478968..b7cb5f1755a8 100644 --- a/spring-context/src/test/java/org/springframework/cache/config/EnableCachingTests.java +++ b/spring-context/src/test/java/org/springframework/cache/config/EnableCachingTests.java @@ -18,11 +18,10 @@ import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.cache.CacheManager; -import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.CachingConfigurer; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.interceptor.CacheErrorHandler; import org.springframework.cache.interceptor.CacheInterceptor; @@ -107,11 +106,8 @@ public void multipleCachingConfigurers() { try { ctx.refresh(); } - catch (BeanCreationException ex) { - Throwable root = ex.getRootCause(); - boolean condition = root instanceof IllegalStateException; - assertThat(condition).isTrue(); - assertThat(root.getMessage().contains("implementations of CachingConfigurer")).isTrue(); + catch (IllegalStateException ex) { + assertThat(ex.getMessage().contains("implementations of CachingConfigurer")).isTrue(); } } @@ -150,7 +146,7 @@ public void bothSetOnlyResolverIsUsed() { @Configuration @EnableCaching - static class EnableCachingConfig extends CachingConfigurerSupport { + static class EnableCachingConfig implements CachingConfigurer { @Override @Bean @@ -227,7 +223,7 @@ public CacheManager cm2() { @Configuration @EnableCaching - static class MultiCacheManagerConfigurer extends CachingConfigurerSupport { + static class MultiCacheManagerConfigurer implements CachingConfigurer { @Bean public CacheManager cm1() { @@ -253,7 +249,7 @@ public KeyGenerator keyGenerator() { @Configuration @EnableCaching - static class EmptyConfigSupportConfig extends CachingConfigurerSupport { + static class EmptyConfigSupportConfig implements CachingConfigurer { @Bean public CacheManager cm() { @@ -264,7 +260,7 @@ public CacheManager cm() { @Configuration @EnableCaching - static class FullCachingConfig extends CachingConfigurerSupport { + static class FullCachingConfig implements CachingConfigurer { @Override @Bean diff --git a/spring-context/src/test/java/org/springframework/cache/config/ExpressionCachingIntegrationTests.java b/spring-context/src/test/java/org/springframework/cache/config/ExpressionCachingIntegrationTests.java index ae109aec5c56..5ba071381fc7 100644 --- a/spring-context/src/test/java/org/springframework/cache/config/ExpressionCachingIntegrationTests.java +++ b/spring-context/src/test/java/org/springframework/cache/config/ExpressionCachingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachePut; -import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.CachingConfigurer; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.concurrent.ConcurrentMapCacheManager; import org.springframework.context.ConfigurableApplicationContext; @@ -122,7 +122,7 @@ public String getId() { @Configuration @EnableCaching - static class SharedConfig extends CachingConfigurerSupport { + static class SharedConfig implements CachingConfigurer { @Override @Bean diff --git a/spring-context/src/test/java/org/springframework/cache/interceptor/CacheErrorHandlerTests.java b/spring-context/src/test/java/org/springframework/cache/interceptor/CacheErrorHandlerTests.java index cfcd9cd713ae..90e26dd51f39 100644 --- a/spring-context/src/test/java/org/springframework/cache/interceptor/CacheErrorHandlerTests.java +++ b/spring-context/src/test/java/org/springframework/cache/interceptor/CacheErrorHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; -import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.CachingConfigurer; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.support.SimpleCacheManager; import org.springframework.cache.support.SimpleValueWrapper; @@ -170,7 +170,7 @@ public void clearFailProperException() { @Configuration @EnableCaching - static class Config extends CachingConfigurerSupport { + static class Config implements CachingConfigurer { @Bean @Override diff --git a/spring-context/src/test/java/org/springframework/cache/interceptor/CachePutEvaluationTests.java b/spring-context/src/test/java/org/springframework/cache/interceptor/CachePutEvaluationTests.java index d17630a8ed29..c257534c6fb0 100644 --- a/spring-context/src/test/java/org/springframework/cache/interceptor/CachePutEvaluationTests.java +++ b/spring-context/src/test/java/org/springframework/cache/interceptor/CachePutEvaluationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; -import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.CachingConfigurer; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.concurrent.ConcurrentMapCacheManager; import org.springframework.context.ConfigurableApplicationContext; @@ -106,7 +106,7 @@ public void getAndPut() { @Configuration @EnableCaching - static class Config extends CachingConfigurerSupport { + static class Config implements CachingConfigurer { @Bean @Override diff --git a/spring-context/src/test/java/org/springframework/cache/interceptor/CacheResolverCustomizationTests.java b/spring-context/src/test/java/org/springframework/cache/interceptor/CacheResolverCustomizationTests.java index ca2e76fc36ff..86f0223402f9 100644 --- a/spring-context/src/test/java/org/springframework/cache/interceptor/CacheResolverCustomizationTests.java +++ b/spring-context/src/test/java/org/springframework/cache/interceptor/CacheResolverCustomizationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.Cacheable; -import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.CachingConfigurer; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -149,7 +149,7 @@ public void unknownCacheResolver() { @Configuration @EnableCaching - static class Config extends CachingConfigurerSupport { + static class Config implements CachingConfigurer { @Override @Bean diff --git a/spring-context/src/test/java/org/springframework/cache/interceptor/CacheSyncFailureTests.java b/spring-context/src/test/java/org/springframework/cache/interceptor/CacheSyncFailureTests.java index 2a3eccc6db7b..de4776adae2e 100644 --- a/spring-context/src/test/java/org/springframework/cache/interceptor/CacheSyncFailureTests.java +++ b/spring-context/src/test/java/org/springframework/cache/interceptor/CacheSyncFailureTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; -import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.CachingConfigurer; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -133,7 +133,7 @@ public Object syncWithTwoGetOperations(Object arg1) { @Configuration @EnableCaching - static class Config extends CachingConfigurerSupport { + static class Config implements CachingConfigurer { @Override @Bean diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java index 477a97dec3df..2599d1a3cefb 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -218,8 +218,8 @@ public void handleExceptionWithListenableFuture() { private void assertFutureWithException(Future result, TestableAsyncUncaughtExceptionHandler exceptionHandler) { assertThatExceptionOfType(ExecutionException.class).isThrownBy( - result::get) - .withCauseExactlyInstanceOf(UnsupportedOperationException.class); + result::get) + .withCauseExactlyInstanceOf(UnsupportedOperationException.class); assertThat(exceptionHandler.isCalled()).as("handler should never be called with Future return type").isFalse(); } @@ -343,7 +343,7 @@ public void execute(Runnable r) { @Configuration @EnableAsync - static class ConfigWithExceptionHandler extends AsyncConfigurerSupport { + static class ConfigWithExceptionHandler implements AsyncConfigurer { @Bean public ITestBean target() { diff --git a/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBeanTests.java b/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBeanTests.java index abba6cf16849..8f81b987c8aa 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBeanTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,12 @@ package org.springframework.scheduling.concurrent; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.FutureTask; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; import org.junit.jupiter.api.Test; @@ -25,10 +29,16 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.GenericApplicationContext; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; /** + * Tests for {@link ThreadPoolExecutorFactoryBean}. + * * @author Juergen Hoeller */ class ThreadPoolExecutorFactoryBeanTests { @@ -44,6 +54,28 @@ void defaultExecutor() throws Exception { context.close(); } + @Test + void executorWithDefaultSettingsDoesNotPrestartAllCoreThreads() { + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBean("taskExecutor", ThreadPoolExecutorFactoryBean.class, TestThreadPoolExecutorFactoryBean::new); + context.refresh(); + ThreadPoolExecutor threadPoolExecutor = context.getBean(ThreadPoolExecutor.class); + verify(threadPoolExecutor, never()).prestartAllCoreThreads(); + } + + @Test + void executorWithPrestartAllCoreThreads() { + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBean("taskExecutor", ThreadPoolExecutorFactoryBean.class, () -> { + TestThreadPoolExecutorFactoryBean factoryBean = new TestThreadPoolExecutorFactoryBean(); + factoryBean.setPrestartAllCoreThreads(true); + return factoryBean; + }); + context.refresh(); + ThreadPoolExecutor threadPoolExecutor = context.getBean(ThreadPoolExecutor.class); + verify(threadPoolExecutor).prestartAllCoreThreads(); + } + @Configuration static class ExecutorConfig { @@ -52,7 +84,19 @@ static class ExecutorConfig { ThreadPoolExecutorFactoryBean executor() { return new ThreadPoolExecutorFactoryBean(); } + } + + @SuppressWarnings("serial") + private static class TestThreadPoolExecutorFactoryBean extends ThreadPoolExecutorFactoryBean { + + @Override + protected ThreadPoolExecutor createExecutor( + int corePoolSize, int maxPoolSize, int keepAliveSeconds, BlockingQueue queue, + ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) { + + return mock(ThreadPoolExecutor.class); + } } } diff --git a/spring-core/src/main/java/org/springframework/asm/ClassReader.java b/spring-core/src/main/java/org/springframework/asm/ClassReader.java index e5c2113993ff..1b14e0e02a38 100644 --- a/spring-core/src/main/java/org/springframework/asm/ClassReader.java +++ b/spring-core/src/main/java/org/springframework/asm/ClassReader.java @@ -194,7 +194,7 @@ public ClassReader( this.b = classFileBuffer; // Check the class' major_version. This field is after the magic and minor_version fields, which // use 4 and 2 bytes respectively. - if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V18) { + if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V19) { throw new IllegalArgumentException( "Unsupported class file major version " + readShort(classFileOffset + 6)); } diff --git a/spring-core/src/main/java/org/springframework/asm/Opcodes.java b/spring-core/src/main/java/org/springframework/asm/Opcodes.java index fbd0c4db640e..dc7bbce285c2 100644 --- a/spring-core/src/main/java/org/springframework/asm/Opcodes.java +++ b/spring-core/src/main/java/org/springframework/asm/Opcodes.java @@ -283,6 +283,7 @@ public interface Opcodes { int V16 = 0 << 16 | 60; int V17 = 0 << 16 | 61; int V18 = 0 << 16 | 62; + int V19 = 0 << 16 | 63; /** * Version flag indicating that the class is using 'preview' features. diff --git a/spring-core/src/main/java/org/springframework/core/log/LogFormatUtils.java b/spring-core/src/main/java/org/springframework/core/log/LogFormatUtils.java index 51ffd98cf0ab..550a202255c2 100644 --- a/spring-core/src/main/java/org/springframework/core/log/LogFormatUtils.java +++ b/spring-core/src/main/java/org/springframework/core/log/LogFormatUtils.java @@ -17,10 +17,12 @@ package org.springframework.core.log; import java.util.function.Function; +import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; /** * Utility methods for formatting and logging messages. @@ -35,10 +37,15 @@ */ public abstract class LogFormatUtils { + private static final Pattern NEWLINE_PATTERN = Pattern.compile("[\n\r]"); + + private static final Pattern CONTROL_CHARACTER_PATTERN = Pattern.compile("\\p{Cc}"); + + /** * Convenience variant of {@link #formatValue(Object, int, boolean)} that * limits the length of a log message to 100 characters and also replaces - * newline characters if {@code limitLength} is set to "true". + * newline and control characters if {@code limitLength} is set to "true". * @param value the value to format * @param limitLength whether to truncate the value at a length of 100 * @return the formatted value @@ -53,25 +60,29 @@ public static String formatValue(@Nullable Object value, boolean limitLength) { * compacting it into a single line when {@code replaceNewLines} is set. * @param value the value to be formatted * @param maxLength the max length, after which to truncate, or -1 for unlimited - * @param replaceNewlines whether to replace newline characters with placeholders + * @param replaceNewlinesAndControlCharacters whether to replace newline and + * control characters with placeholders * @return the formatted value */ - public static String formatValue(@Nullable Object value, int maxLength, boolean replaceNewlines) { + public static String formatValue( + @Nullable Object value, int maxLength, boolean replaceNewlinesAndControlCharacters) { + if (value == null) { return ""; } String result; try { - result = value.toString(); + result = ObjectUtils.nullSafeToString(value); } catch (Throwable ex) { - result = ex.toString(); + result = ObjectUtils.nullSafeToString(ex); } if (maxLength != -1) { result = (result.length() > maxLength ? result.substring(0, maxLength) + " (truncated)..." : result); } - if (replaceNewlines) { - result = result.replace("\n", "").replace("\r", ""); + if (replaceNewlinesAndControlCharacters) { + result = NEWLINE_PATTERN.matcher(result).replaceAll(""); + result = CONTROL_CHARACTER_PATTERN.matcher(result).replaceAll("?"); } if (value instanceof CharSequence) { result = "\"" + result + "\""; diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java index 60f0a26ff40e..fddd853af521 100644 --- a/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java @@ -162,7 +162,7 @@ public Set getAnnotatedMethods(String annotationName) { throw new IllegalStateException("Failed to introspect annotated methods on " + getIntrospectedClass(), ex); } } - return annotatedMethods != null ? annotatedMethods : Collections.emptySet(); + return (annotatedMethods != null ? annotatedMethods : Collections.emptySet()); } diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleAnnotationMetadata.java b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleAnnotationMetadata.java index 5f1ebf4bb51e..c80bc172a270 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleAnnotationMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleAnnotationMetadata.java @@ -127,6 +127,11 @@ public String[] getMemberClassNames() { return this.memberClassNames.clone(); } + @Override + public MergedAnnotations getAnnotations() { + return this.annotations; + } + @Override public Set getAnnotationTypes() { Set annotationTypes = this.annotationTypes; @@ -149,13 +154,9 @@ public Set getAnnotatedMethods(String annotationName) { annotatedMethods.add(annotatedMethod); } } - return annotatedMethods != null ? annotatedMethods : Collections.emptySet(); + return (annotatedMethods != null ? annotatedMethods : Collections.emptySet()); } - @Override - public MergedAnnotations getAnnotations() { - return this.annotations; - } @Override public boolean equals(@Nullable Object obj) { diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataReadingVisitor.java index 6422cda99105..a951194ae7dc 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataReadingVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -99,8 +99,7 @@ public void visitOuterClass(String owner, String name, String desc) { } @Override - public void visitInnerClass(String name, @Nullable String outerName, String innerName, - int access) { + public void visitInnerClass(String name, @Nullable String outerName, String innerName, int access) { if (outerName != null) { String className = toClassName(name); String outerClassName = toClassName(outerName); diff --git a/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java index d304f08d2c00..63eea1e9eab8 100644 --- a/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java +++ b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java @@ -733,6 +733,10 @@ else if (this.pattern != null) { } for (int i = 1; i <= matcher.groupCount(); i++) { String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } String value = matcher.group(i); uriTemplateVariables.put(name, value); } diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java index ba58532c74f2..3ca17918b53e 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1159,6 +1159,20 @@ void isAssignableFromForComplexWildcards() throws Exception { assertThatResolvableType(complex4).isNotAssignableFrom(complex3); } + @Test + void identifyTypeVariable() throws Exception { + Method method = ClassArguments.class.getMethod("typedArgumentFirst", Class.class, Class.class, Class.class); + ResolvableType returnType = ResolvableType.forMethodReturnType(method, ClassArguments.class); + + ResolvableType arg0 = ResolvableType.forMethodParameter(method, 0, ClassArguments.class); + ResolvableType arg1 = ResolvableType.forMethodParameter(method, 1, ClassArguments.class); + ResolvableType arg2 = ResolvableType.forMethodParameter(method, 2, ClassArguments.class); + + assertThat(returnType.getType().equals(arg0.as(Class.class).getGeneric(0).getType())).isTrue(); + assertThat(returnType.getType().equals(arg1.as(Class.class).getGeneric(0).getType())).isFalse(); + assertThat(returnType.getType().equals(arg2.as(Class.class).getGeneric(0).getType())).isFalse(); + } + @Test void hashCodeAndEquals() throws Exception { ResolvableType forClass = ResolvableType.forClass(List.class); @@ -1427,6 +1441,10 @@ interface Methods { } + interface TypedMethods extends Methods { + } + + static class AssignmentBase { public O o; @@ -1479,7 +1497,9 @@ static class Assignment extends AssignmentBase { } - interface TypedMethods extends Methods { + interface ClassArguments { + + T typedArgumentFirst(Class arg0, Class arg1, Class arg2); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java index 821c0b246ae6..e9b62ec2adbb 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java @@ -20,6 +20,7 @@ import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.util.List; +import java.util.Optional; import org.springframework.core.MethodParameter; import org.springframework.core.convert.TypeDescriptor; @@ -290,20 +291,29 @@ static boolean convertArguments(TypeConverter converter, Object[] arguments, Exe Object argument = arguments[varargsPosition]; TypeDescriptor targetType = new TypeDescriptor(methodParam); TypeDescriptor sourceType = TypeDescriptor.forObject(argument); - // If the argument type is equal to the varargs element type, there is no need - // to convert it or wrap it in an array. For example, using StringToArrayConverter - // to convert a String containing a comma would result in the String being split - // and repackaged in an array when it should be used as-is. - if (!sourceType.equals(targetType.getElementTypeDescriptor())) { + if (argument == null) { + // Perform the equivalent of GenericConversionService.convertNullSource() for a single argument. + if (targetType.getElementTypeDescriptor().getObjectType() == Optional.class) { + arguments[varargsPosition] = Optional.empty(); + conversionOccurred = true; + } + } + // If the argument type is equal to the varargs element type, there is no need to + // convert it or wrap it in an array. For example, using StringToArrayConverter to + // convert a String containing a comma would result in the String being split and + // repackaged in an array when it should be used as-is. + else if (!sourceType.equals(targetType.getElementTypeDescriptor())) { arguments[varargsPosition] = converter.convertValue(argument, sourceType, targetType); } - // Three outcomes of the above if-block: - // 1) the input argument was correct type but not wrapped in an array, and nothing was done. - // 2) the input argument was already compatible (i.e., array of valid type), and nothing was done. - // 3) the input argument was the wrong type and got converted and wrapped in an array. + // Possible outcomes of the above if-else block: + // 1) the input argument was null, and nothing was done. + // 2) the input argument was null; the varargs element type is Optional; and the argument was converted to Optional.empty(). + // 3) the input argument was correct type but not wrapped in an array, and nothing was done. + // 4) the input argument was already compatible (i.e., array of valid type), and nothing was done. + // 5) the input argument was the wrong type and got converted and wrapped in an array. if (argument != arguments[varargsPosition] && !isFirstEntryInArray(argument, arguments[varargsPosition])) { - conversionOccurred = true; // case 3 + conversionOccurred = true; // case 5 } } // Otherwise, convert remaining arguments to the varargs element type. diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java index b2cde1f10ff3..6a94550104d3 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java @@ -234,26 +234,33 @@ public void testAddingMethodResolvers() { @Test public void testVarargsInvocation01() { - // Calling 'public int aVarargsMethod(String... strings)' - returns number of arguments - evaluate("aVarargsMethod('a','b','c')", 3, Integer.class); - evaluate("aVarargsMethod('a')", 1, Integer.class); - evaluate("aVarargsMethod()", 0, Integer.class); - evaluate("aVarargsMethod(1,2,3)", 3, Integer.class); // all need converting to strings - evaluate("aVarargsMethod(1)", 1, Integer.class); // needs string conversion - evaluate("aVarargsMethod(1,'a',3.0d)", 3, Integer.class); // first and last need conversion - evaluate("aVarargsMethod(new String[]{'a','b','c'})", 3, Integer.class); + // Calling 'public String aVarargsMethod(String... strings)' + evaluate("aVarargsMethod('a','b','c')", "[a, b, c]", String.class); + evaluate("aVarargsMethod('a')", "[a]", String.class); + evaluate("aVarargsMethod()", "[]", String.class); + evaluate("aVarargsMethod(1,2,3)", "[1, 2, 3]", String.class); // all need converting to strings + evaluate("aVarargsMethod(1)", "[1]", String.class); // needs string conversion + evaluate("aVarargsMethod(1,'a',3.0d)", "[1, a, 3.0]", String.class); // first and last need conversion + evaluate("aVarargsMethod(new String[]{'a','b','c'})", "[a, b, c]", String.class); + evaluate("aVarargsMethod(new String[]{})", "[]", String.class); + evaluate("aVarargsMethod(null)", "[null]", String.class); + evaluate("aVarargsMethod(null,'a')", "[null, a]", String.class); + evaluate("aVarargsMethod('a',null,'b')", "[a, null, b]", String.class); } @Test public void testVarargsInvocation02() { - // Calling 'public int aVarargsMethod2(int i, String... strings)' - returns int + length_of_strings - evaluate("aVarargsMethod2(5,'a','b','c')", 8, Integer.class); - evaluate("aVarargsMethod2(2,'a')", 3, Integer.class); - evaluate("aVarargsMethod2(4)", 4, Integer.class); - evaluate("aVarargsMethod2(8,2,3)", 10, Integer.class); - evaluate("aVarargsMethod2(9)", 9, Integer.class); - evaluate("aVarargsMethod2(2,'a',3.0d)", 4, Integer.class); - evaluate("aVarargsMethod2(8,new String[]{'a','b','c'})", 11, Integer.class); + // Calling 'public String aVarargsMethod2(int i, String... strings)' + evaluate("aVarargsMethod2(5,'a','b','c')", "5-[a, b, c]", String.class); + evaluate("aVarargsMethod2(2,'a')", "2-[a]", String.class); + evaluate("aVarargsMethod2(4)", "4-[]", String.class); + evaluate("aVarargsMethod2(8,2,3)", "8-[2, 3]", String.class); + evaluate("aVarargsMethod2(2,'a',3.0d)", "2-[a, 3.0]", String.class); + evaluate("aVarargsMethod2(8,new String[]{'a','b','c'})", "8-[a, b, c]", String.class); + evaluate("aVarargsMethod2(8,new String[]{})", "8-[]", String.class); + evaluate("aVarargsMethod2(8,null)", "8-[null]", String.class); + evaluate("aVarargsMethod2(8,null,'a')", "8-[null, a]", String.class); + evaluate("aVarargsMethod2(8,'a',null,'b')", "8-[a, null, b]", String.class); } @Test @@ -284,6 +291,22 @@ public void testVarargsInvocation03() { evaluate("aVarargsMethod3('foo', 'bar,baz')", "foo-bar,baz", String.class); } + @Test + public void testVarargsOptionalInvocation() { + // Calling 'public String optionalVarargsMethod(Optional... values)' + evaluate("optionalVarargsMethod()", "[]", String.class); + evaluate("optionalVarargsMethod(new String[0])", "[]", String.class); + evaluate("optionalVarargsMethod('a')", "[Optional[a]]", String.class); + evaluate("optionalVarargsMethod('a','b','c')", "[Optional[a], Optional[b], Optional[c]]", String.class); + evaluate("optionalVarargsMethod(9)", "[Optional[9]]", String.class); + evaluate("optionalVarargsMethod(2,3)", "[Optional[2], Optional[3]]", String.class); + evaluate("optionalVarargsMethod('a',3.0d)", "[Optional[a], Optional[3.0]]", String.class); + evaluate("optionalVarargsMethod(new String[]{'a','b','c'})", "[Optional[a], Optional[b], Optional[c]]", String.class); + evaluate("optionalVarargsMethod(null)", "[Optional.empty]", String.class); + evaluate("optionalVarargsMethod(null,'a')", "[Optional.empty, Optional[a]]", String.class); + evaluate("optionalVarargsMethod('a',null,'b')", "[Optional[a], Optional.empty, Optional[b]]", String.class); + } + @Test public void testInvocationOnNullContextObject() { evaluateAndCheckError("null.toString()",SpelMessage.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java b/spring-expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java index eb60acd79808..4bb3f7da0cc3 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.expression.spel; +import java.util.Arrays; import java.util.GregorianCalendar; import org.springframework.expression.spel.support.StandardEvaluationContext; @@ -51,10 +52,10 @@ private static void populateFunctions(StandardEvaluationContext testContext) { TestScenarioCreator.class.getDeclaredMethod("reverseInt", Integer.TYPE, Integer.TYPE, Integer.TYPE)); testContext.registerFunction("reverseString", TestScenarioCreator.class.getDeclaredMethod("reverseString", String.class)); - testContext.registerFunction("varargsFunctionReverseStringsAndMerge", - TestScenarioCreator.class.getDeclaredMethod("varargsFunctionReverseStringsAndMerge", String[].class)); - testContext.registerFunction("varargsFunctionReverseStringsAndMerge2", - TestScenarioCreator.class.getDeclaredMethod("varargsFunctionReverseStringsAndMerge2", Integer.TYPE, String[].class)); + testContext.registerFunction("varargsFunction", + TestScenarioCreator.class.getDeclaredMethod("varargsFunction", String[].class)); + testContext.registerFunction("varargsFunction2", + TestScenarioCreator.class.getDeclaredMethod("varargsFunction2", Integer.TYPE, String[].class)); } catch (Exception ex) { throw new IllegalStateException(ex); @@ -108,25 +109,12 @@ public static String reverseString(String input) { return backwards.toString(); } - public static String varargsFunctionReverseStringsAndMerge(String... strings) { - StringBuilder sb = new StringBuilder(); - if (strings != null) { - for (int i = strings.length - 1; i >= 0; i--) { - sb.append(strings[i]); - } - } - return sb.toString(); + public static String varargsFunction(String... strings) { + return Arrays.toString(strings); } - public static String varargsFunctionReverseStringsAndMerge2(int j, String... strings) { - StringBuilder sb = new StringBuilder(); - sb.append(j); - if (strings != null) { - for (int i = strings.length - 1; i >= 0; i--) { - sb.append(strings[i]); - } - } - return sb.toString(); + public static String varargsFunction2(int i, String... strings) { + return String.valueOf(i) + "-" + Arrays.toString(strings); } } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/VariableAndFunctionTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/VariableAndFunctionTests.java index 3271e45ea6d1..71c266acd801 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/VariableAndFunctionTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/VariableAndFunctionTests.java @@ -59,21 +59,33 @@ public void testFunctionAccess02() { @Test public void testCallVarargsFunction() { - evaluate("#varargsFunctionReverseStringsAndMerge('a,b')", "a,b", String.class); - evaluate("#varargsFunctionReverseStringsAndMerge('a', 'b,c', 'd')", "db,ca", String.class); - evaluate("#varargsFunctionReverseStringsAndMerge('a','b','c')", "cba", String.class); - evaluate("#varargsFunctionReverseStringsAndMerge('a')", "a", String.class); - evaluate("#varargsFunctionReverseStringsAndMerge()", "", String.class); - evaluate("#varargsFunctionReverseStringsAndMerge('b',25)", "25b", String.class); - evaluate("#varargsFunctionReverseStringsAndMerge(25)", "25", String.class); + evaluate("#varargsFunction()", "[]", String.class); + evaluate("#varargsFunction(new String[0])", "[]", String.class); + evaluate("#varargsFunction('a')", "[a]", String.class); + evaluate("#varargsFunction('a','b','c')", "[a, b, c]", String.class); + // Conversion from int to String + evaluate("#varargsFunction(25)", "[25]", String.class); + evaluate("#varargsFunction('b',25)", "[b, 25]", String.class); + // Strings that contain a comma + evaluate("#varargsFunction('a,b')", "[a,b]", String.class); + evaluate("#varargsFunction('a', 'x,y', 'd')", "[a, x,y, d]", String.class); + // null values + evaluate("#varargsFunction(null)", "[null]", String.class); + evaluate("#varargsFunction('a',null,'b')", "[a, null, b]", String.class); - evaluate("#varargsFunctionReverseStringsAndMerge2(1, 'a,b')", "1a,b", String.class); - evaluate("#varargsFunctionReverseStringsAndMerge2(1,'a','b','c')", "1cba", String.class); - evaluate("#varargsFunctionReverseStringsAndMerge2(1, 'a', 'b,c', 'd')", "1db,ca", String.class); - evaluate("#varargsFunctionReverseStringsAndMerge2(2,'a')", "2a", String.class); - evaluate("#varargsFunctionReverseStringsAndMerge2(3)", "3", String.class); - evaluate("#varargsFunctionReverseStringsAndMerge2(4,'b',25)", "425b", String.class); - evaluate("#varargsFunctionReverseStringsAndMerge2(5,25)", "525", String.class); + evaluate("#varargsFunction2(9)", "9-[]", String.class); + evaluate("#varargsFunction2(9, new String[0])", "9-[]", String.class); + evaluate("#varargsFunction2(9,'a')", "9-[a]", String.class); + evaluate("#varargsFunction2(9,'a','b','c')", "9-[a, b, c]", String.class); + // Conversion from int to String + evaluate("#varargsFunction2(9,25)", "9-[25]", String.class); + evaluate("#varargsFunction2(9,'b',25)", "9-[b, 25]", String.class); + // Strings that contain a comma: + evaluate("#varargsFunction2(9, 'a,b')", "9-[a,b]", String.class); + evaluate("#varargsFunction2(9, 'a', 'x,y', 'd')", "9-[a, x,y, d]", String.class); + // null values + evaluate("#varargsFunction2(9,null)", "9-[null]", String.class); + evaluate("#varargsFunction2(9,'a',null,'b')", "9-[a, null, b]", String.class); } @Test diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java b/spring-expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java index 34960d982fe3..282622cf7d2d 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java @@ -17,11 +17,13 @@ package org.springframework.expression.spel.testresources; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import org.springframework.util.ObjectUtils; @@ -191,16 +193,17 @@ public String joinThreeStrings(String a, String b, String c) { return a + b + c; } - public int aVarargsMethod(String... strings) { - if (strings == null) - return 0; - return strings.length; + public String aVarargsMethod(String... strings) { + return Arrays.toString(strings); } - public int aVarargsMethod2(int i, String... strings) { - if (strings == null) - return i; - return strings.length + i; + public String aVarargsMethod2(int i, String... strings) { + return String.valueOf(i) + "-" + Arrays.toString(strings); + } + + @SuppressWarnings("unchecked") + public String optionalVarargsMethod(Optional... values) { + return Arrays.toString(values); } public String aVarargsMethod3(String str1, String... strings) { diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/object/StoredProcedure.java b/spring-jdbc/src/main/java/org/springframework/jdbc/object/StoredProcedure.java index d6a24e5d8b07..f740eb4b792f 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/object/StoredProcedure.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/object/StoredProcedure.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -87,6 +87,8 @@ protected boolean allowsUnusedParameters() { * they appear in the database's stored procedure parameter list. *

Names are purely used to help mapping. * @param param the parameter object + * @throws InvalidDataAccessApiUsageException if the parameter has no name, or if the + * operation is already compiled, and hence cannot be configured further */ @Override public void declareParameter(SqlParameter param) throws InvalidDataAccessApiUsageException { diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/lookup/AbstractRoutingDataSourceTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/lookup/AbstractRoutingDataSourceTests.java new file mode 100644 index 000000000000..8dc23a3f9651 --- /dev/null +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/lookup/AbstractRoutingDataSourceTests.java @@ -0,0 +1,161 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.jdbc.datasource.lookup; + + +import java.util.HashMap; +import java.util.Map; + +import javax.sql.DataSource; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + + +/** + * Tests for {@link AbstractRoutingDataSource}. + * + * @author Kazuki Shimizu + */ +class AbstractRoutingDataSourceTests { + + @Test + void setTargetDataSources() { + final ThreadLocal lookupKey = new ThreadLocal<>(); + AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() { + @Override + protected Object determineCurrentLookupKey() { + return lookupKey.get(); + } + }; + DataSource ds1 = new StubDataSource(); + DataSource ds2 = new StubDataSource(); + + MapDataSourceLookup dataSourceLookup = new MapDataSourceLookup(); + dataSourceLookup.addDataSource("dataSource2", ds2); + routingDataSource.setDataSourceLookup(dataSourceLookup); + + Map targetDataSources = new HashMap<>(); + targetDataSources.put("ds1", ds1); + targetDataSources.put("ds2", "dataSource2"); + routingDataSource.setTargetDataSources(targetDataSources); + + routingDataSource.afterPropertiesSet(); + lookupKey.set("ds1"); + assertThat(routingDataSource.determineTargetDataSource()).isSameAs(ds1); + lookupKey.set("ds2"); + assertThat(routingDataSource.determineTargetDataSource()).isSameAs(ds2); + } + + @Test + void targetDataSourcesIsNull() { + AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() { + @Override + protected Object determineCurrentLookupKey() { + return null; + } + }; + assertThatIllegalArgumentException().isThrownBy(routingDataSource::afterPropertiesSet) + .withMessage("Property 'targetDataSources' is required"); + } + + @Test + void dataSourceIsUnSupportedType() { + AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() { + @Override + protected Object determineCurrentLookupKey() { + return null; + } + }; + Map targetDataSources = new HashMap<>(); + targetDataSources.put("ds1", 1); + routingDataSource.setTargetDataSources(targetDataSources); + assertThatIllegalArgumentException().isThrownBy(routingDataSource::afterPropertiesSet) + .withMessage("Illegal data source value - only [javax.sql.DataSource] and String supported: 1"); + } + + + @Test + void setDefaultTargetDataSource() { + final ThreadLocal lookupKey = new ThreadLocal<>(); + AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() { + @Override + protected Object determineCurrentLookupKey() { + return lookupKey.get(); + } + }; + DataSource ds = new StubDataSource(); + routingDataSource.setTargetDataSources(new HashMap<>()); + routingDataSource.setDefaultTargetDataSource(ds); + routingDataSource.afterPropertiesSet(); + lookupKey.set("foo"); + assertThat(routingDataSource.determineTargetDataSource()).isSameAs(ds); + } + + @Test + void setDefaultTargetDataSourceFallbackIsFalse() { + final ThreadLocal lookupKey = new ThreadLocal<>(); + AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() { + @Override + protected Object determineCurrentLookupKey() { + return lookupKey.get(); + } + }; + DataSource ds = new StubDataSource(); + routingDataSource.setTargetDataSources(new HashMap<>()); + routingDataSource.setDefaultTargetDataSource(ds); + routingDataSource.setLenientFallback(false); + routingDataSource.afterPropertiesSet(); + lookupKey.set("foo"); + assertThatIllegalStateException().isThrownBy(routingDataSource::determineTargetDataSource) + .withMessage("Cannot determine target DataSource for lookup key [foo]"); + } + + @Test + void setDefaultTargetDataSourceLookupKeyIsNullWhenFallbackIsFalse() { + final ThreadLocal lookupKey = new ThreadLocal<>(); + AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() { + @Override + protected Object determineCurrentLookupKey() { + return lookupKey.get(); + } + }; + DataSource ds = new StubDataSource(); + routingDataSource.setTargetDataSources(new HashMap<>()); + routingDataSource.setDefaultTargetDataSource(ds); + routingDataSource.setLenientFallback(false); + routingDataSource.afterPropertiesSet(); + lookupKey.set(null); + assertThat(routingDataSource.determineTargetDataSource()).isSameAs(ds); + } + + @Test + public void notInitialized() { + AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() { + @Override + protected Object determineCurrentLookupKey() { + return null; + } + }; + assertThatIllegalArgumentException().isThrownBy(routingDataSource::determineTargetDataSource) + .withMessage("DataSource router not initialized"); + } + +} diff --git a/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java b/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java index ccb10d27c172..3a22e4b6e8fe 100644 --- a/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java +++ b/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java @@ -56,6 +56,7 @@ import com.thoughtworks.xstream.io.xml.DomWriter; import com.thoughtworks.xstream.io.xml.QNameMap; import com.thoughtworks.xstream.io.xml.SaxWriter; +import com.thoughtworks.xstream.io.xml.StaxDriver; import com.thoughtworks.xstream.io.xml.StaxReader; import com.thoughtworks.xstream.io.xml.StaxWriter; import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder; @@ -696,7 +697,14 @@ protected void marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter) t @Override protected void marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter) throws XmlMappingException { try { - doMarshal(graph, new StaxWriter(new QNameMap(), streamWriter, this.nameCoder), null); + StaxWriter writer; + if (this.streamDriver instanceof StaxDriver) { + writer = ((StaxDriver) this.streamDriver).createStaxWriter(streamWriter); + } + else { + writer = new StaxWriter(new QNameMap(), streamWriter, this.nameCoder); + } + doMarshal(graph, writer, null); } catch (XMLStreamException ex) { throw convertXStreamException(ex, true); diff --git a/spring-oxm/src/test/java/org/springframework/oxm/xstream/XStreamMarshallerTests.java b/spring-oxm/src/test/java/org/springframework/oxm/xstream/XStreamMarshallerTests.java index 304d3fcc771b..44435fff9720 100644 --- a/spring-oxm/src/test/java/org/springframework/oxm/xstream/XStreamMarshallerTests.java +++ b/spring-oxm/src/test/java/org/springframework/oxm/xstream/XStreamMarshallerTests.java @@ -43,6 +43,8 @@ import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver; import com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver; import com.thoughtworks.xstream.io.json.JsonWriter; +import com.thoughtworks.xstream.io.xml.QNameMap; +import com.thoughtworks.xstream.io.xml.StaxDriver; import com.thoughtworks.xstream.security.AnyTypePermission; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -176,6 +178,23 @@ void marshalStaxResultXMLStreamWriter() throws Exception { assertThat(XmlContent.from(writer)).isSimilarTo(EXPECTED_STRING); } + @Test + void marshalStaxResultXMLStreamWriterDefaultNamespace() throws Exception { + QNameMap map = new QNameMap(); + map.setDefaultNamespace("https://example.com"); + map.setDefaultPrefix("spr"); + StaxDriver driver = new StaxDriver(map); + marshaller.setStreamDriver(driver); + + XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); + StringWriter writer = new StringWriter(); + XMLStreamWriter streamWriter = outputFactory.createXMLStreamWriter(writer); + Result result = StaxUtils.createStaxResult(streamWriter); + marshaller.marshal(flight, result); + assertThat(XmlContent.from(writer)).isSimilarTo( + "42"); + } + @Test void marshalStaxResultXMLEventWriter() throws Exception { XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); diff --git a/spring-test/spring-test.gradle b/spring-test/spring-test.gradle index 0bc316636a7a..58ca0680ad44 100644 --- a/spring-test/spring-test.gradle +++ b/spring-test/spring-test.gradle @@ -60,9 +60,6 @@ dependencies { testImplementation("org.hibernate:hibernate-core") testImplementation("org.hibernate:hibernate-validator") testImplementation("javax.validation:validation-api") - testImplementation("org.junit.platform:junit-platform-runner") { - exclude group: "junit", module: "junit" - } testImplementation("org.junit.platform:junit-platform-testkit") testImplementation("com.fasterxml.jackson.core:jackson-databind") testImplementation("com.thoughtworks.xstream:xstream") diff --git a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java index 2ae169bd2959..318d9d0f8c2c 100644 --- a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java @@ -112,8 +112,8 @@ public class MergedContextConfiguration implements Serializable { * @param activeProfiles the merged active bean definition profiles * @param contextLoader the resolved {@code ContextLoader} */ - public MergedContextConfiguration(Class testClass, String[] locations, Class[] classes, - String[] activeProfiles, ContextLoader contextLoader) { + public MergedContextConfiguration(Class testClass, @Nullable String[] locations, @Nullable Class[] classes, + @Nullable String[] activeProfiles, @Nullable ContextLoader contextLoader) { this(testClass, locations, classes, null, activeProfiles, contextLoader); } @@ -128,9 +128,9 @@ public MergedContextConfiguration(Class testClass, String[] locations, Class< * @param activeProfiles the merged active bean definition profiles * @param contextLoader the resolved {@code ContextLoader} */ - public MergedContextConfiguration(Class testClass, String[] locations, Class[] classes, + public MergedContextConfiguration(Class testClass, @Nullable String[] locations, @Nullable Class[] classes, @Nullable Set>> contextInitializerClasses, - String[] activeProfiles, ContextLoader contextLoader) { + @Nullable String[] activeProfiles, @Nullable ContextLoader contextLoader) { this(testClass, locations, classes, contextInitializerClasses, activeProfiles, contextLoader, null, null); } @@ -149,9 +149,9 @@ public MergedContextConfiguration(Class testClass, String[] locations, Class< * @param parent the parent configuration or {@code null} if there is no parent * @since 3.2.2 */ - public MergedContextConfiguration(Class testClass, String[] locations, Class[] classes, + public MergedContextConfiguration(Class testClass, @Nullable String[] locations, @Nullable Class[] classes, @Nullable Set>> contextInitializerClasses, - String[] activeProfiles, ContextLoader contextLoader, + @Nullable String[] activeProfiles, @Nullable ContextLoader contextLoader, @Nullable CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, @Nullable MergedContextConfiguration parent) { @@ -197,7 +197,7 @@ public MergedContextConfiguration(MergedContextConfiguration mergedConfig) { public MergedContextConfiguration(Class testClass, @Nullable String[] locations, @Nullable Class[] classes, @Nullable Set>> contextInitializerClasses, @Nullable String[] activeProfiles, @Nullable String[] propertySourceLocations, - @Nullable String[] propertySourceProperties, ContextLoader contextLoader, + @Nullable String[] propertySourceProperties, @Nullable ContextLoader contextLoader, @Nullable CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, @Nullable MergedContextConfiguration parent) { @@ -235,7 +235,7 @@ public MergedContextConfiguration(Class testClass, @Nullable String[] locatio @Nullable Set>> contextInitializerClasses, @Nullable String[] activeProfiles, @Nullable String[] propertySourceLocations, @Nullable String[] propertySourceProperties, @Nullable Set contextCustomizers, - ContextLoader contextLoader, @Nullable CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, + @Nullable ContextLoader contextLoader, @Nullable CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, @Nullable MergedContextConfiguration parent) { this.testClass = testClass; diff --git a/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java b/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java index ec883b02a41c..fed5808a1ff2 100644 --- a/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -193,7 +193,7 @@ private RequestMatcher formData(MultiValueMap expectedMap, boole *

  • {@link Resource} - content from a file *
  • {@code byte[]} - other raw content * - *

    Note: This method uses the Apache Commons File Upload + *

    Note: This method uses the Apache Commons FileUpload * library to parse the multipart data and it must be on the test classpath. * @param expectedMap the expected multipart values * @since 5.3 diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java index c5271e8d18e8..4a64b49d9943 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -314,6 +314,15 @@ public A findAnnotationOnBean(String beanName, Class a return this.beanFactory.findAnnotationOnBean(beanName, annotationType); } + @Override + @Nullable + public A findAnnotationOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException { + + return this.beanFactory.findAnnotationOnBean(beanName, annotationType, allowFactoryBeanInit); + } + //--------------------------------------------------------------------- // Implementation of HierarchicalBeanFactory interface diff --git a/spring-test/src/test/java/org/springframework/test/context/event/EventPublishingTestExecutionListenerIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/event/EventPublishingTestExecutionListenerIntegrationTests.java index c3edfeb83869..7cba9a363509 100644 --- a/spring-test/src/test/java/org/springframework/test/context/event/EventPublishingTestExecutionListenerIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/event/EventPublishingTestExecutionListenerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.Async; -import org.springframework.scheduling.annotation.AsyncConfigurerSupport; +import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; @@ -73,13 +73,17 @@ public class EventPublishingTestExecutionListenerIntegrationTests { private static final CountDownLatch countDownLatch = new CountDownLatch(1); private final TestContextManager testContextManager = new TestContextManager(ExampleTestCase.class); + private final TestContext testContext = testContextManager.getTestContext(); + // Note that the following invocation of getApplicationContext() forces eager // loading of the test's ApplicationContext which consequently results in the // publication of all test execution events. Otherwise, TestContext#publishEvent // would never fire any events for ExampleTestCase. private final TestExecutionListener listener = testContext.getApplicationContext().getBean(TestExecutionListener.class); + private final Object testInstance = new ExampleTestCase(); + private final Method traceableTestMethod = ReflectionUtils.findMethod(ExampleTestCase.class, "traceableTest"); @@ -127,8 +131,8 @@ public void beforeTestMethodAnnotationWithFailingCondition() throws Exception { public void beforeTestMethodAnnotationWithFailingEventListener() throws Exception { Method method = ReflectionUtils.findMethod(ExampleTestCase.class, "testWithFailingEventListener"); assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> - testContextManager.beforeTestMethod(testInstance, method)) - .withMessageContaining("Boom!"); + testContextManager.beforeTestMethod(testInstance, method)) + .withMessageContaining("Boom!"); verify(listener, only()).beforeTestMethod(testContext); } @@ -149,7 +153,7 @@ public void beforeTestMethodAnnotationWithFailingAsyncEventListener() throws Exc verify(listener, only()).beforeTestMethod(testContext); assertThat(TrackingAsyncUncaughtExceptionHandler.asyncException.getMessage()) - .startsWith("Asynchronous exception for test method [" + methodName + "] in thread [" + THREAD_NAME_PREFIX); + .startsWith("Asynchronous exception for test method [" + methodName + "] in thread [" + THREAD_NAME_PREFIX); } @Test @@ -211,7 +215,7 @@ public void testWithFailingAsyncEventListener() { @Configuration @EnableAsync(proxyTargetClass = true) - static class TestEventListenerConfiguration extends AsyncConfigurerSupport { + static class TestEventListenerConfiguration implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { @@ -306,7 +310,7 @@ static class AsyncTestEventComponent { public void beforeTestMethodWithAsyncFailure(BeforeTestMethodEvent event) throws Exception { this.listener.beforeTestMethod(event.getSource()); throw new RuntimeException(String.format("Asynchronous exception for test method [%s] in thread [%s]", - event.getTestContext().getTestMethod().getName(), Thread.currentThread().getName())); + event.getTestContext().getTestMethod().getName(), Thread.currentThread().getName())); } } diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java index 460105d9ea56..0e885081fa23 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,7 +57,7 @@ public abstract class AbstractTransactionManagementConfiguration implements Impo @Override public void setImportMetadata(AnnotationMetadata importMetadata) { this.enableTx = AnnotationAttributes.fromMap( - importMetadata.getAnnotationAttributes(EnableTransactionManagement.class.getName(), false)); + importMetadata.getAnnotationAttributes(EnableTransactionManagement.class.getName())); if (this.enableTx == null) { throw new IllegalArgumentException( "@EnableTransactionManagement is not present on importing class " + importMetadata.getClassName()); diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java index 80983806eded..c19cf2c10670 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -163,7 +163,7 @@ protected Object getCacheKey(Method method, @Nullable Class targetClass) { */ @Nullable protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class targetClass) { - // Don't allow no-public methods as required. + // Don't allow non-public methods, as configured. if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; } diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpConnector.java b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpConnector.java index 8112ecf6442e..0fdfaab69155 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpConnector.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpConnector.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -73,18 +73,21 @@ public ReactorClientHttpConnector() { * @since 5.1 */ public ReactorClientHttpConnector(ReactorResourceFactory factory, Function mapper) { - this.httpClient = defaultInitializer.andThen(mapper).apply(initHttpClient(factory)); + ConnectionProvider provider = factory.getConnectionProvider(); + Assert.notNull(provider, "No ConnectionProvider: is ReactorResourceFactory not initialized yet?"); + this.httpClient = defaultInitializer.andThen(mapper).andThen(applyLoopResources(factory)) + .apply(HttpClient.create(provider)); } - @SuppressWarnings("deprecation") - private static HttpClient initHttpClient(ReactorResourceFactory resourceFactory) { - ConnectionProvider provider = resourceFactory.getConnectionProvider(); - LoopResources resources = resourceFactory.getLoopResources(); - Assert.notNull(provider, "No ConnectionProvider: is ReactorResourceFactory not initialized yet?"); - Assert.notNull(resources, "No LoopResources: is ReactorResourceFactory not initialized yet?"); - return HttpClient.create(provider).tcpConfiguration(tcpClient -> tcpClient.runOn(resources)); + private static Function applyLoopResources(ReactorResourceFactory factory) { + return httpClient -> { + LoopResources resources = factory.getLoopResources(); + Assert.notNull(resources, "No LoopResources: is ReactorResourceFactory not initialized yet?"); + return httpClient.runOn(resources); + }; } + /** * Constructor with a pre-configured {@code HttpClient} instance. * @param httpClient the client to use diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerWriteProcessor.java b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerWriteProcessor.java index 92d7b41846b5..e94e3dcc334c 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerWriteProcessor.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerWriteProcessor.java @@ -254,7 +254,7 @@ protected void dataReceived(T data) { * the next item from the upstream, write Publisher. *

    The default implementation is a no-op. * @deprecated originally introduced for Undertow to stop write notifications - * when no data is available, but deprecated as of as of 5.0.6 since constant + * when no data is available, but deprecated as of 5.0.6 since constant * switching on every requested item causes a significant slowdown. */ @Deprecated diff --git a/spring-web/src/main/java/org/springframework/web/filter/DelegatingFilterProxy.java b/spring-web/src/main/java/org/springframework/web/filter/DelegatingFilterProxy.java index bfbe62739dce..0dbc22408240 100644 --- a/spring-web/src/main/java/org/springframework/web/filter/DelegatingFilterProxy.java +++ b/spring-web/src/main/java/org/springframework/web/filter/DelegatingFilterProxy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,9 +54,9 @@ * of the {@code Filter.init} and {@code Filter.destroy} lifecycle methods * on the target bean, letting the servlet container manage the filter lifecycle. * - *

    As of Spring 3.1, {@code DelegatingFilterProxy} has been updated to optionally accept - * constructor parameters when using Servlet 3.0's instance-based filter registration - * methods, usually in conjunction with Spring 3.1's + *

    As of Spring 3.1, {@code DelegatingFilterProxy} has been updated to optionally + * accept constructor parameters when using a Servlet container's instance-based filter + * registration methods, usually in conjunction with Spring's * {@link org.springframework.web.WebApplicationInitializer} SPI. These constructors allow * for providing the delegate Filter bean directly, or providing the application context * and bean name to fetch, avoiding the need to look up the application context from the @@ -100,8 +100,7 @@ public class DelegatingFilterProxy extends GenericFilterBean { /** - * Create a new {@code DelegatingFilterProxy}. For traditional (pre-Servlet 3.0) use - * in {@code web.xml}. + * Create a new {@code DelegatingFilterProxy}. For traditional use in {@code web.xml}. * @see #setTargetBeanName(String) */ public DelegatingFilterProxy() { @@ -111,8 +110,7 @@ public DelegatingFilterProxy() { * Create a new {@code DelegatingFilterProxy} with the given {@link Filter} delegate. * Bypasses entirely the need for interacting with a Spring application context, * specifying the {@linkplain #setTargetBeanName target bean name}, etc. - *

    For use in Servlet 3.0+ environments where instance-based registration of - * filters is supported. + *

    For use with instance-based registration of filters. * @param delegate the {@code Filter} instance that this proxy will delegate to and * manage the lifecycle for (must not be {@code null}). * @see #doFilter(ServletRequest, ServletResponse, FilterChain) @@ -130,9 +128,8 @@ public DelegatingFilterProxy(Filter delegate) { * bean from the Spring {@code WebApplicationContext} found in the {@code ServletContext} * (either the 'root' application context or the context named by * {@link #setContextAttribute}). - *

    For use in Servlet 3.0+ environments where instance-based registration of - * filters is supported. - *

    The target bean must implement the standard Servlet Filter. + *

    For use with instance-based registration of filters. + *

    The target bean must implement the standard Servlet Filter interface. * @param targetBeanName name of the target filter bean to look up in the Spring * application context (must not be {@code null}). * @see #findWebApplicationContext() @@ -145,8 +142,7 @@ public DelegatingFilterProxy(String targetBeanName) { /** * Create a new {@code DelegatingFilterProxy} that will retrieve the named target * bean from the given Spring {@code WebApplicationContext}. - *

    For use in Servlet 3.0+ environments where instance-based registration of - * filters is supported. + *

    For use with instance-based registration of filters. *

    The target bean must implement the standard Servlet Filter interface. *

    The given {@code WebApplicationContext} may or may not be refreshed when passed * in. If it has not, and if the context implements {@link ConfigurableApplicationContext}, diff --git a/spring-web/src/main/java/org/springframework/web/filter/OncePerRequestFilter.java b/spring-web/src/main/java/org/springframework/web/filter/OncePerRequestFilter.java index 0f5e1a3831ad..271560f930ca 100644 --- a/spring-web/src/main/java/org/springframework/web/filter/OncePerRequestFilter.java +++ b/spring-web/src/main/java/org/springframework/web/filter/OncePerRequestFilter.java @@ -98,12 +98,10 @@ public final void doFilter(ServletRequest request, ServletResponse response, Fil boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null; if (skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) { - // Proceed without invoking this filter... filterChain.doFilter(request, response); } else if (hasAlreadyFilteredAttribute) { - if (DispatcherType.ERROR.equals(request.getDispatcherType())) { doFilterNestedErrorDispatch(httpRequest, httpResponse, filterChain); return; @@ -197,7 +195,7 @@ protected boolean shouldNotFilter(HttpServletRequest request) throws ServletExce * setting up thread locals or to perform final processing at the very end. *

    Note that although a filter can be mapped to handle specific dispatcher * types via {@code web.xml} or in Java through the {@code ServletContext}, - * servlet containers may enforce different defaults with regards to + * servlet containers may enforce different defaults with respect to * dispatcher types. This flag enforces the design intent of the filter. *

    The default return value is "true", which means the filter will not be * invoked during subsequent async dispatches. If "false", the filter will diff --git a/spring-web/src/main/java/org/springframework/web/server/MediaTypeNotSupportedStatusException.java b/spring-web/src/main/java/org/springframework/web/server/MediaTypeNotSupportedStatusException.java index 690e7a136b0f..f9cd1700a435 100644 --- a/spring-web/src/main/java/org/springframework/web/server/MediaTypeNotSupportedStatusException.java +++ b/spring-web/src/main/java/org/springframework/web/server/MediaTypeNotSupportedStatusException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ * @author Rossen Stoyanchev * @since 5.0 * @deprecated in favor of {@link UnsupportedMediaTypeStatusException}, - * with this class never thrown by Spring code and to be removed in 5.3 + * with this class never thrown by Spring code and to be removed in 6.0 */ @Deprecated @SuppressWarnings("serial") diff --git a/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java b/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java index faa0ccbf6431..104f762b4539 100644 --- a/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java +++ b/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java @@ -48,7 +48,6 @@ *

    Used e.g. by {@link org.springframework.web.filter.AbstractRequestLoggingFilter}. * Note: As of Spring Framework 5.0, this wrapper is built on the Servlet 3.1 API. * - * * @author Juergen Hoeller * @author Brian Clozel * @since 4.1.3 diff --git a/spring-web/src/main/java/org/springframework/web/util/HtmlCharacterEntityDecoder.java b/spring-web/src/main/java/org/springframework/web/util/HtmlCharacterEntityDecoder.java index 2277edfff3ad..2d6db518b20a 100644 --- a/spring-web/src/main/java/org/springframework/web/util/HtmlCharacterEntityDecoder.java +++ b/spring-web/src/main/java/org/springframework/web/util/HtmlCharacterEntityDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -72,7 +72,7 @@ private void findNextPotentialReference(int startPosition) { boolean isPotentialReference = (this.nextPotentialReferencePosition != -1 && this.nextSemicolonPosition != -1 && - this.nextPotentialReferencePosition - this.nextSemicolonPosition < MAX_REFERENCE_SIZE); + this.nextSemicolonPosition - this.nextPotentialReferencePosition < MAX_REFERENCE_SIZE); if (isPotentialReference) { break; diff --git a/spring-web/src/main/java/org/springframework/web/util/UriUtils.java b/spring-web/src/main/java/org/springframework/web/util/UriUtils.java index a67be402e567..d6653c24f148 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriUtils.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriUtils.java @@ -407,7 +407,7 @@ public static String extractFileExtension(String path) { int paramIndex = path.indexOf(';', begin); end = (paramIndex != -1 && paramIndex < end ? paramIndex : end); int extIndex = path.lastIndexOf('.', end); - if (extIndex != -1 && extIndex > begin) { + if (extIndex != -1 && extIndex >= begin) { return path.substring(extIndex + 1, end); } return null; diff --git a/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java b/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java index 685f376b426a..ac43d5394c9c 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java +++ b/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java @@ -578,8 +578,8 @@ private String decodeInternal(HttpServletRequest request, String source) { return UriUtils.decode(source, enc); } catch (UnsupportedCharsetException ex) { - if (logger.isWarnEnabled()) { - logger.warn("Could not decode request string [" + source + "] with encoding '" + enc + + if (logger.isDebugEnabled()) { + logger.debug("Could not decode request string [" + source + "] with encoding '" + enc + "': falling back to platform default encoding; exception message: " + ex.getMessage()); } return URLDecoder.decode(source); diff --git a/spring-web/src/test/java/org/springframework/web/util/HtmlUtilsTests.java b/spring-web/src/test/java/org/springframework/web/util/HtmlUtilsTests.java index 68d31898f54f..232a436e503c 100644 --- a/spring-web/src/test/java/org/springframework/web/util/HtmlUtilsTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/HtmlUtilsTests.java @@ -28,7 +28,7 @@ public class HtmlUtilsTests { @Test - public void testHtmlEscape() { + void testHtmlEscape() { String unescaped = "\"This is a quote'"; String escaped = HtmlUtils.htmlEscape(unescaped); assertThat(escaped).isEqualTo(""This is a quote'"); @@ -39,14 +39,14 @@ public void testHtmlEscape() { } @Test - public void testHtmlUnescape() { + void testHtmlUnescape() { String escaped = ""This is a quote'"; String unescaped = HtmlUtils.htmlUnescape(escaped); assertThat(unescaped).isEqualTo("\"This is a quote'"); } @Test - public void testEncodeIntoHtmlCharacterSet() { + void testEncodeIntoHtmlCharacterSet() { assertThat(HtmlUtils.htmlEscape("")).as("An empty string should be converted to an empty string").isEqualTo(""); assertThat(HtmlUtils.htmlEscape("A sentence containing no special characters.")).as("A string containing no special characters should not be affected").isEqualTo("A sentence containing no special characters."); @@ -62,7 +62,7 @@ public void testEncodeIntoHtmlCharacterSet() { // SPR-9293 @Test - public void testEncodeIntoHtmlCharacterSetFromUtf8() { + void testEncodeIntoHtmlCharacterSetFromUtf8() { String utf8 = ("UTF-8"); assertThat(HtmlUtils.htmlEscape("", utf8)).as("An empty string should be converted to an empty string").isEqualTo(""); assertThat(HtmlUtils.htmlEscape("A sentence containing no special characters.")).as("A string containing no special characters should not be affected").isEqualTo("A sentence containing no special characters."); @@ -74,7 +74,7 @@ public void testEncodeIntoHtmlCharacterSetFromUtf8() { } @Test - public void testDecodeFromHtmlCharacterSet() { + void testDecodeFromHtmlCharacterSet() { assertThat(HtmlUtils.htmlUnescape("")).as("An empty string should be converted to an empty string").isEqualTo(""); assertThat(HtmlUtils.htmlUnescape("This is a sentence containing no special characters.")).as("A string containing no special characters should not be affected").isEqualTo("This is a sentence containing no special characters."); diff --git a/spring-web/src/test/java/org/springframework/web/util/UriUtilsTests.java b/spring-web/src/test/java/org/springframework/web/util/UriUtilsTests.java index 019107a535c4..f9dae3a38362 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UriUtilsTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UriUtilsTests.java @@ -133,6 +133,7 @@ public void extractFileExtension() { assertThat(UriUtils.extractFileExtension("/products;q=11/view.html?param=/path/a.do")).isEqualTo("html"); assertThat(UriUtils.extractFileExtension("/products;q=11/view.html;r=22?param=/path/a.do")).isEqualTo("html"); assertThat(UriUtils.extractFileExtension("/products;q=11/view.html;r=22;s=33?param=/path/a.do")).isEqualTo("html"); + assertThat(UriUtils.extractFileExtension("/products/.html")).isEqualTo("html"); } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java index 7e8219c1678a..1df8c06ffbaa 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java @@ -519,18 +519,15 @@ interface RequestHeadersSpec> { * scenarios, for example to decode the response differently depending * on the response status: *

    -		 * Mono<Object> entityMono = client.get()
    +		 * Mono<Person> entityMono = client.get()
     		 *     .uri("/persons/1")
     		 *     .accept(MediaType.APPLICATION_JSON)
     		 *     .exchangeToMono(response -> {
     		 *         if (response.statusCode().equals(HttpStatus.OK)) {
     		 *             return response.bodyToMono(Person.class);
     		 *         }
    -		 *         else if (response.statusCode().is4xxClientError()) {
    -		 *             return response.bodyToMono(ErrorContainer.class);
    -		 *         }
     		 *         else {
    -		 *             return Mono.error(response.createException());
    +		 *             return response.createException().flatMap(Mono::error);
     		 *         }
     		 *     });
     		 * 
    @@ -551,18 +548,15 @@ interface RequestHeadersSpec> { * scenarios, for example to decode the response differently depending * on the response status: *

    -		 * Mono<Object> entityMono = client.get()
    +		 * Flux<Person> entityMono = client.get()
     		 *     .uri("/persons")
     		 *     .accept(MediaType.APPLICATION_JSON)
     		 *     .exchangeToFlux(response -> {
     		 *         if (response.statusCode().equals(HttpStatus.OK)) {
     		 *             return response.bodyToFlux(Person.class);
     		 *         }
    -		 *         else if (response.statusCode().is4xxClientError()) {
    -		 *             return response.bodyToMono(ErrorContainer.class).flux();
    -		 *         }
     		 *         else {
    -		 *             return Flux.error(response.createException());
    +		 *             return response.createException().flatMapMany(Mono::error);
     		 *         }
     		 *     });
     		 * 
    diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java index da71fbe565a1..2d406d39d46c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java @@ -362,9 +362,7 @@ private void writeStatusAndHeaders(ServerHttpResponse response) { private static void copy(MultiValueMap src, MultiValueMap dst) { if (!src.isEmpty()) { - src.entrySet().stream() - .filter(entry -> !dst.containsKey(entry.getKey())) - .forEach(entry -> dst.put(entry.getKey(), entry.getValue())); + dst.putAll(src); } } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java index 30157153d4ae..739fc17a1c33 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java @@ -32,10 +32,12 @@ import reactor.core.publisher.Mono; import org.springframework.core.io.Resource; +import org.springframework.http.HttpStatus; import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.util.Assert; import org.springframework.web.reactive.result.view.ViewResolver; +import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebHandler; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; @@ -1231,9 +1233,6 @@ public List viewResolvers() { private static class RouterFunctionWebHandler implements WebHandler { - private static final HandlerFunction NOT_FOUND_HANDLER = - request -> ServerResponse.notFound().build(); - private final HandlerStrategies strategies; private final RouterFunction routerFunction; @@ -1249,7 +1248,7 @@ public Mono handle(ServerWebExchange exchange) { ServerRequest request = new DefaultServerRequest(exchange, this.strategies.messageReaders()); addAttributes(exchange, request); return this.routerFunction.route(request) - .defaultIfEmpty(notFound()) + .switchIfEmpty(createNotFoundError()) .flatMap(handlerFunction -> wrapException(() -> handlerFunction.handle(request))) .flatMap(response -> wrapException(() -> response.writeTo(exchange, new HandlerStrategiesResponseContext(this.strategies)))); @@ -1261,9 +1260,9 @@ private void addAttributes(ServerWebExchange exchange, ServerRequest request) { attributes.put(REQUEST_ATTRIBUTE, request); } - @SuppressWarnings("unchecked") - private static HandlerFunction notFound() { - return (HandlerFunction) NOT_FOUND_HANDLER; + private Mono createNotFoundError() { + return Mono.defer(() -> Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND, + "No matching router function"))); } private static Mono wrapException(Supplier> supplier) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/PathResourceResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/PathResourceResolver.java index dd86b8e9a264..23d5eaaa55f3 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/PathResourceResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/PathResourceResolver.java @@ -29,6 +29,7 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; +import org.springframework.core.log.LogFormatUtils; import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; @@ -119,11 +120,12 @@ protected Mono getResource(String resourcePath, Resource location) { return Mono.just(resource); } else if (logger.isWarnEnabled()) { - Resource[] allowedLocations = getAllowedLocations(); - logger.warn("Resource path \"" + resourcePath + "\" was successfully resolved " + - "but resource \"" + resource.getURL() + "\" is neither under the " + - "current location \"" + location.getURL() + "\" nor under any of the " + - "allowed locations " + (allowedLocations != null ? Arrays.asList(allowedLocations) : "[]")); + Resource[] allowed = getAllowedLocations(); + logger.warn(LogFormatUtils.formatValue( + "Resource path \"" + resourcePath + "\" was successfully resolved " + + "but resource \"" + resource.getURL() + "\" is neither under the " + + "current location \"" + location.getURL() + "\" nor under any of the " + + "allowed locations " + (allowed != null ? Arrays.asList(allowed) : "[]"), -1, true)); } } return Mono.empty(); @@ -199,7 +201,8 @@ private boolean isInvalidEncodedPath(String resourcePath) { try { String decodedPath = URLDecoder.decode(resourcePath, "UTF-8"); if (decodedPath.contains("../") || decodedPath.contains("..\\")) { - logger.warn("Resolved resource path contains encoded \"../\" or \"..\\\": " + resourcePath); + logger.warn(LogFormatUtils.formatValue( + "Resolved resource path contains encoded \"../\" or \"..\\\": " + resourcePath, -1, true)); return true; } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java index e6f0640a7022..7c4db588b450 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java @@ -39,6 +39,7 @@ import org.springframework.core.codec.Hints; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; +import org.springframework.core.log.LogFormatUtils; import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -572,7 +573,8 @@ private boolean isInvalidEncodedPath(String path) { protected boolean isInvalidPath(String path) { if (path.contains("WEB-INF") || path.contains("META-INF")) { if (logger.isWarnEnabled()) { - logger.warn("Path with \"WEB-INF\" or \"META-INF\": [" + path + "]"); + logger.warn(LogFormatUtils.formatValue( + "Path with \"WEB-INF\" or \"META-INF\": [" + path + "]", -1, true)); } return true; } @@ -580,14 +582,16 @@ protected boolean isInvalidPath(String path) { String relativePath = (path.charAt(0) == '/' ? path.substring(1) : path); if (ResourceUtils.isUrl(relativePath) || relativePath.startsWith("url:")) { if (logger.isWarnEnabled()) { - logger.warn("Path represents URL or has \"url:\" prefix: [" + path + "]"); + logger.warn(LogFormatUtils.formatValue( + "Path represents URL or has \"url:\" prefix: [" + path + "]", -1, true)); } return true; } } if (path.contains("..") && StringUtils.cleanPath(path).contains("../")) { if (logger.isWarnEnabled()) { - logger.warn("Path contains \"../\" after call to StringUtils#cleanPath: [" + path + "]"); + logger.warn(LogFormatUtils.formatValue( + "Path contains \"../\" after call to StringUtils#cleanPath: [" + path + "]", -1, true)); } return true; } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/TomcatRequestUpgradeStrategy.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/TomcatRequestUpgradeStrategy.java index f918410dd5ee..1d2c641c9bea 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/TomcatRequestUpgradeStrategy.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/TomcatRequestUpgradeStrategy.java @@ -123,6 +123,7 @@ public Integer getMaxBinaryMessageBufferSize() { } + @SuppressWarnings("deprecation") // for old doUpgrade variant in Tomcat 9.0.55 @Override public Mono upgrade(ServerWebExchange exchange, WebSocketHandler handler, @Nullable String subProtocol, Supplier handshakeInfoFactory){ diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilderTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilderTests.java index 38b295463ca4..c3780d2c443d 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilderTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilderTests.java @@ -320,6 +320,23 @@ public void copyCookies() { assertThat(serverResponse.block().cookies().isEmpty()).isFalse(); } + @Test + public void overwriteHeaders() { + ServerResponse serverResponse = + ServerResponse.ok().headers(headers -> headers.set("Foo", "Bar")).build().block(); + assertThat(serverResponse).isNotNull(); + + MockServerWebExchange mockExchange = MockServerWebExchange + .builder(MockServerHttpRequest.get("https://example.org")) + .build(); + MockServerHttpResponse response = mockExchange.getResponse(); + response.getHeaders().set("Foo", "Baz"); + + serverResponse.writeTo(mockExchange, EMPTY_CONTEXT).block(); + + assertThat(response.getHeaders().getFirst("Foo")).isEqualTo("Bar"); + } + @Test public void build() { diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionsTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionsTests.java index 46b29bdf6cd4..ad6bfbdca9f7 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionsTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionsTests.java @@ -16,14 +16,17 @@ package org.springframework.web.reactive.function.server; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.Test; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseCookie; @@ -191,6 +194,27 @@ public void toHttpHandlerHandlerResponseStatusException() { assertThat(httpResponse.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); } + @Test + public void toHttpHandlerRouteNotFoundReturnsResponseStatusException() { + HandlerFunction handlerFunction = request -> ServerResponse.accepted().build(); + RouterFunction routerFunction = + RouterFunctions.route(RequestPredicates.GET("/path"), handlerFunction); + + HandlerStrategies handlerStrategies = HandlerStrategies.empty().exceptionHandler((exchange, ex) -> { + exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND); + DataBuffer buffer = exchange.getResponse().bufferFactory().wrap("Custom response".getBytes(StandardCharsets.UTF_8)); + return exchange.getResponse().writeWith(Flux.just(buffer)); + }).build(); + HttpHandler result = RouterFunctions.toHttpHandler(routerFunction, handlerStrategies); + assertThat(result).isNotNull(); + + MockServerHttpRequest httpRequest = MockServerHttpRequest.get("https://localhost").build(); + MockServerHttpResponse httpResponse = new MockServerHttpResponse(); + result.handle(httpRequest, httpResponse).block(); + assertThat(httpResponse.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + assertThat(httpResponse.getBodyAsString().block()).isEqualTo("Custom response"); + } + @Test public void toHttpHandlerHandlerReturnResponseStatusExceptionInResponseWriteTo() { HandlerFunction handlerFunction = diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java index 869b4c6979c5..cd4dc70872b3 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java @@ -1395,6 +1395,7 @@ protected void render(ModelAndView mv, HttpServletRequest request, HttpServletRe } try { if (mv.getStatus() != null) { + request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, mv.getStatus()); response.setStatus(mv.getStatus().value()); } view.render(mv.getModelInternal(), request, response); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java index 81d38fb3b8c7..7ee392b3514a 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java @@ -103,14 +103,6 @@ public HandlerMappingIntrospector(ApplicationContext context) { } - /** - * Return the configured or detected {@code HandlerMapping}s. - */ - public List getHandlerMappings() { - return (this.handlerMappings != null ? this.handlerMappings : Collections.emptyList()); - } - - @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; @@ -125,6 +117,13 @@ public void afterPropertiesSet() { } } + /** + * Return the configured or detected {@code HandlerMapping}s. + */ + public List getHandlerMappings() { + return (this.handlerMappings != null ? this.handlerMappings : Collections.emptyList()); + } + /** * Find the {@link HandlerMapping} that would handle the given request and diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinder.java index a0db1cac45a2..2a7489b98176 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -69,9 +69,8 @@ protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) if (uriVars != null) { uriVars.forEach((name, value) -> { if (mpvs.contains(name)) { - if (logger.isWarnEnabled()) { - logger.warn("Skipping URI variable '" + name + - "' because request contains bind value with same name."); + if (logger.isDebugEnabled()) { + logger.debug("URI variable '" + name + "' overridden by request bind value."); } } else { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ReactiveTypeHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ReactiveTypeHandler.java index ec3e9a47081c..107fc4cd7b86 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ReactiveTypeHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ReactiveTypeHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -202,7 +202,7 @@ private void logExecutorWarning(MethodParameter returnType) { "-------------------------------\n" + "Controller:\t" + returnType.getContainingClass().getName() + "\n" + "Method:\t\t" + returnType.getMethod().getName() + "\n" + - "Returning:\t" + ResolvableType.forMethodParameter(returnType).toString() + "\n" + + "Returning:\t" + ResolvableType.forMethodParameter(returnType) + "\n" + "!!!"); this.taskExecutorWarning = false; } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java index 1da7ca1ed8b3..c3b264158891 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java @@ -187,7 +187,6 @@ public void setEmbeddedValueResolver(StringValueResolver resolver) { @Override @SuppressWarnings("deprecation") public void afterPropertiesSet() { - this.config = new RequestMappingInfo.BuilderConfiguration(); this.config.setTrailingSlashMatch(useTrailingSlashMatch()); this.config.setContentNegotiationManager(getContentNegotiationManager()); @@ -246,6 +245,19 @@ public List getFileExtensions() { return this.config.getFileExtensions(); } + /** + * Obtain a {@link RequestMappingInfo.BuilderConfiguration} that can reflects + * the internal configuration of this {@code HandlerMapping} and can be used + * to set {@link RequestMappingInfo.Builder#options(RequestMappingInfo.BuilderConfiguration)}. + *

    This is useful for programmatic registration of request mappings via + * {@link #registerHandlerMethod(Object, Method, RequestMappingInfo)}. + * @return the builder configuration that reflects the internal state + * @since 5.3.14 + */ + public RequestMappingInfo.BuilderConfiguration getBuilderConfiguration() { + return this.config; + } + /** * {@inheritDoc} @@ -390,6 +402,19 @@ public void registerMapping(RequestMappingInfo mapping, Object handler, Method m updateConsumesCondition(mapping, method); } + /** + * {@inheritDoc} + *

    Note: To create the {@link RequestMappingInfo}, + * please use {@link #getBuilderConfiguration()} and set the options on + * {@link RequestMappingInfo.Builder#options(RequestMappingInfo.BuilderConfiguration)} + * to match how this {@code HandlerMapping} is configured. This + * is important for example to ensure use of + * {@link org.springframework.web.util.pattern.PathPattern} or + * {@link org.springframework.util.PathMatcher} based matching. + * @param handler the bean name of the handler or the handler instance + * @param method the method to register + * @param mapping the mapping conditions associated with the handler method + */ @Override protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) { super.registerHandlerMethod(handler, method, mapping); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/PathResourceResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/PathResourceResolver.java index 2b97ac60895f..cc042115d88d 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/PathResourceResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/PathResourceResolver.java @@ -33,6 +33,7 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; +import org.springframework.core.log.LogFormatUtils; import org.springframework.http.server.PathContainer; import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; @@ -190,11 +191,12 @@ protected Resource getResource(String resourcePath, Resource location) throws IO return resource; } else if (logger.isWarnEnabled()) { - Resource[] allowedLocations = getAllowedLocations(); - logger.warn("Resource path \"" + resourcePath + "\" was successfully resolved " + - "but resource \"" + resource.getURL() + "\" is neither under the " + - "current location \"" + location.getURL() + "\" nor under any of the " + - "allowed locations " + (allowedLocations != null ? Arrays.asList(allowedLocations) : "[]")); + Resource[] allowed = getAllowedLocations(); + logger.warn(LogFormatUtils.formatValue( + "Resource path \"" + resourcePath + "\" was successfully resolved " + + "but resource \"" + resource.getURL() + "\" is neither under " + + "the current location \"" + location.getURL() + "\" nor under any of " + + "the allowed locations " + (allowed != null ? Arrays.asList(allowed) : "[]"), -1, true)); } } return null; @@ -297,7 +299,8 @@ private boolean isInvalidEncodedPath(String resourcePath) { try { String decodedPath = URLDecoder.decode(resourcePath, "UTF-8"); if (decodedPath.contains("../") || decodedPath.contains("..\\")) { - logger.warn("Resolved resource path contains encoded \"../\" or \"..\\\": " + resourcePath); + logger.warn(LogFormatUtils.formatValue( + "Resolved resource path contains encoded \"../\" or \"..\\\": " + resourcePath, -1, true)); return true; } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java index 7855a1171cb3..ba1b0ef6cd88 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java @@ -39,6 +39,7 @@ import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; +import org.springframework.core.log.LogFormatUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRange; @@ -734,7 +735,8 @@ private boolean isInvalidEncodedPath(String path) { protected boolean isInvalidPath(String path) { if (path.contains("WEB-INF") || path.contains("META-INF")) { if (logger.isWarnEnabled()) { - logger.warn("Path with \"WEB-INF\" or \"META-INF\": [" + path + "]"); + logger.warn(LogFormatUtils.formatValue( + "Path with \"WEB-INF\" or \"META-INF\": [" + path + "]", -1, true)); } return true; } @@ -742,14 +744,16 @@ protected boolean isInvalidPath(String path) { String relativePath = (path.charAt(0) == '/' ? path.substring(1) : path); if (ResourceUtils.isUrl(relativePath) || relativePath.startsWith("url:")) { if (logger.isWarnEnabled()) { - logger.warn("Path represents URL or has \"url:\" prefix: [" + path + "]"); + logger.warn(LogFormatUtils.formatValue( + "Path represents URL or has \"url:\" prefix: [" + path + "]", -1, true)); } return true; } } if (path.contains("..") && StringUtils.cleanPath(path).contains("../")) { if (logger.isWarnEnabled()) { - logger.warn("Path contains \"../\" after call to StringUtils#cleanPath: [" + path + "]"); + logger.warn(LogFormatUtils.formatValue( + "Path contains \"../\" after call to StringUtils#cleanPath: [" + path + "]", -1, true)); } return true; } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/TagWriter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/TagWriter.java index 87cedb540223..a8167bcbf34a 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/TagWriter.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/TagWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -98,6 +98,18 @@ public void writeAttribute(String attributeName, String attributeValue) throws J .append(attributeValue).append("\""); } + /** + * Variant of {@link #writeAttribute(String, String)} for writing empty HTML + * attributes without a value such as {@code required}. + * @since 5.3.14 + */ + public void writeAttribute(String attributeName) throws JspException { + if (currentState().isBlockTag()) { + throw new IllegalStateException("Cannot write attributes after opening tag is closed."); + } + this.writer.append(" ").append(attributeName); + } + /** * Write an HTML attribute if the supplied value is not {@code null} * or zero length. diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/PathMatchingUrlHandlerMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/PathMatchingUrlHandlerMappingTests.java index 5e4e148c8511..4c988f1a7fd0 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/PathMatchingUrlHandlerMappingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/PathMatchingUrlHandlerMappingTests.java @@ -155,13 +155,15 @@ void actualPathMatching(SimpleUrlHandlerMapping mapping, WebApplicationContext w chain = getHandler(mapping, wac, request); assertThat(chain.getHandler()).isSameAs(defaultBean); - request = new MockHttpServletRequest("GET", "/administrator/testing/longer/bla"); - chain = getHandler(mapping, wac, request); - assertThat(chain.getHandler()).isSameAs(bean); - - request = new MockHttpServletRequest("GET", "/administrator/testing/longer/test.jsp"); - chain = getHandler(mapping, wac, request); - assertThat(chain.getHandler()).isSameAs(bean); + if (mapping.getPatternParser() != null) { + request = new MockHttpServletRequest("GET", "/administrator/testing/longer/bla"); + chain = getHandler(mapping, wac, request); + assertThat(chain.getHandler()).isSameAs(bean); + + request = new MockHttpServletRequest("GET", "/administrator/testing/longer/test.jsp"); + chain = getHandler(mapping, wac, request); + assertThat(chain.getHandler()).isSameAs(bean); + } request = new MockHttpServletRequest("GET", "/administrator/testing/longer2/notmatching/notmatching"); chain = getHandler(mapping, wac, request); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java index f60e69af0e3b..9ed74e07817e 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,6 +79,17 @@ static Stream pathPatternsArguments() { return Stream.of(Arguments.of(mapping1, wac1), Arguments.of(mapping2, wac2)); } + @Test + void builderConfiguration() { + RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping(); + mapping.setApplicationContext(new StaticWebApplicationContext()); + + RequestMappingInfo.BuilderConfiguration config = mapping.getBuilderConfiguration(); + assertThat(config).isNotNull(); + + mapping.afterPropertiesSet(); + assertThat(mapping.getBuilderConfiguration()).isNotNull().isNotSameAs(config); + } @Test @SuppressWarnings("deprecation") @@ -88,7 +99,8 @@ void useRegisteredSuffixPatternMatch() { handlerMapping.setApplicationContext(new StaticWebApplicationContext()); Map fileExtensions = Collections.singletonMap("json", MediaType.APPLICATION_JSON); - org.springframework.web.accept.PathExtensionContentNegotiationStrategy strategy = new org.springframework.web.accept.PathExtensionContentNegotiationStrategy(fileExtensions); + org.springframework.web.accept.PathExtensionContentNegotiationStrategy strategy = + new org.springframework.web.accept.PathExtensionContentNegotiationStrategy(fileExtensions); ContentNegotiationManager manager = new ContentNegotiationManager(strategy); handlerMapping.setContentNegotiationManager(manager); @@ -104,7 +116,8 @@ void useRegisteredSuffixPatternMatch() { @SuppressWarnings("deprecation") void useRegisteredSuffixPatternMatchInitialization() { Map fileExtensions = Collections.singletonMap("json", MediaType.APPLICATION_JSON); - org.springframework.web.accept.PathExtensionContentNegotiationStrategy strategy = new org.springframework.web.accept.PathExtensionContentNegotiationStrategy(fileExtensions); + org.springframework.web.accept.PathExtensionContentNegotiationStrategy strategy = + new org.springframework.web.accept.PathExtensionContentNegotiationStrategy(fileExtensions); ContentNegotiationManager manager = new ContentNegotiationManager(strategy); final Set extensions = new HashSet<>(); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java index 6957bdfc828e..aeaf049adb23 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java @@ -1897,6 +1897,18 @@ void modelAndViewWithStatus(boolean usePathPatterns) throws Exception { assertThat(response.getForwardedUrl()).isEqualTo("view"); } + @PathPatternsParameterizedTest + void modelAndViewWithStatusForRedirect(boolean usePathPatterns) throws Exception { + initDispatcherServlet(ModelAndViewController.class, usePathPatterns); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/redirect"); + MockHttpServletResponse response = new MockHttpServletResponse(); + getServlet().service(request, response); + + assertThat(response.getStatus()).isEqualTo(307); + assertThat(response.getRedirectedUrl()).isEqualTo("/path"); + } + @PathPatternsParameterizedTest // SPR-14796 void modelAndViewWithStatusInExceptionHandler(boolean usePathPatterns) throws Exception { initDispatcherServlet(ModelAndViewController.class, usePathPatterns); @@ -3872,6 +3884,11 @@ public ModelAndView methodWithHttpStatus(MyEntity object) { return new ModelAndView("view", HttpStatus.UNPROCESSABLE_ENTITY); } + @RequestMapping("/redirect") + public ModelAndView methodWithHttpStatusForRedirect(MyEntity object) { + return new ModelAndView("redirect:/path", HttpStatus.TEMPORARY_REDIRECT); + } + @RequestMapping("/exception") public void raiseException() throws Exception { throw new TestException(); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/TomcatRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/TomcatRequestUpgradeStrategy.java index c3c6a191609d..4f685ad372bd 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/TomcatRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/TomcatRequestUpgradeStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,6 +53,7 @@ public String[] getSupportedVersions() { return new String[] {"13"}; } + @SuppressWarnings("deprecation") // for old doUpgrade variant in Tomcat 9.0.55 @Override public void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, @Nullable String selectedProtocol, List selectedExtensions, Endpoint endpoint) diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractHandshakeHandler.java index 8ca56429a4e8..4c9fa3ea2dd9 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractHandshakeHandler.java @@ -29,6 +29,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.context.Lifecycle; +import org.springframework.core.log.LogFormatUtils; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.server.ServerHttpRequest; @@ -299,7 +300,8 @@ public final boolean doHandshake(ServerHttpRequest request, ServerHttpResponse r protected void handleInvalidUpgradeHeader(ServerHttpRequest request, ServerHttpResponse response) throws IOException { if (logger.isErrorEnabled()) { - logger.error("Handshake failed due to invalid Upgrade header: " + request.getHeaders().getUpgrade()); + logger.error(LogFormatUtils.formatValue( + "Handshake failed due to invalid Upgrade header: " + request.getHeaders().getUpgrade(), -1, true)); } response.setStatusCode(HttpStatus.BAD_REQUEST); response.getBody().write("Can \"Upgrade\" only to \"WebSocket\".".getBytes(StandardCharsets.UTF_8)); @@ -307,7 +309,8 @@ protected void handleInvalidUpgradeHeader(ServerHttpRequest request, ServerHttpR protected void handleInvalidConnectHeader(ServerHttpRequest request, ServerHttpResponse response) throws IOException { if (logger.isErrorEnabled()) { - logger.error("Handshake failed due to invalid Connection header " + request.getHeaders().getConnection()); + logger.error(LogFormatUtils.formatValue( + "Handshake failed due to invalid Connection header" + request.getHeaders().getConnection(), -1, true)); } response.setStatusCode(HttpStatus.BAD_REQUEST); response.getBody().write("\"Connection\" must be \"upgrade\".".getBytes(StandardCharsets.UTF_8)); @@ -331,8 +334,9 @@ protected String[] getSupportedVersions() { protected void handleWebSocketVersionNotSupported(ServerHttpRequest request, ServerHttpResponse response) { if (logger.isErrorEnabled()) { String version = request.getHeaders().getFirst("Sec-WebSocket-Version"); - logger.error("Handshake failed due to unsupported WebSocket version: " + version + - ". Supported versions: " + Arrays.toString(getSupportedVersions())); + logger.error(LogFormatUtils.formatValue( + "Handshake failed due to unsupported WebSocket version: " + version + + ". Supported versions: " + Arrays.toString(getSupportedVersions()), -1, true)); } response.setStatusCode(HttpStatus.UPGRADE_REQUIRED); response.getHeaders().set(WebSocketHttpHeaders.SEC_WEBSOCKET_VERSION, diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java index 84502556bd65..39f6d2436b19 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java @@ -32,6 +32,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.core.log.LogFormatUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -377,7 +378,8 @@ public final void handleRequest(ServerHttpRequest request, ServerHttpResponse re if (sockJsPath == null) { if (logger.isWarnEnabled()) { - logger.warn("Expected SockJS path. Failing request: " + request.getURI()); + logger.warn(LogFormatUtils.formatValue( + "Expected SockJS path. Failing request: " + request.getURI(), -1, true)); } response.setStatusCode(HttpStatus.NOT_FOUND); return; @@ -447,7 +449,8 @@ else if (requestInfo != null) { String[] pathSegments = StringUtils.tokenizeToStringArray(sockJsPath.substring(1), "/"); if (pathSegments.length != 3) { if (logger.isWarnEnabled()) { - logger.warn("Invalid SockJS path '" + sockJsPath + "' - required to have 3 path segments"); + logger.warn(LogFormatUtils.formatValue("Invalid SockJS path '" + sockJsPath + "' - " + + "required to have 3 path segments", -1, true)); } if (requestInfo != null) { logger.debug("Ignoring transport request: " + requestInfo); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlingSockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlingSockJsService.java index 8968980ef6ae..18224e3184f4 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlingSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlingSockJsService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import java.util.concurrent.ScheduledFuture; import org.springframework.context.Lifecycle; +import org.springframework.core.log.LogFormatUtils; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.server.ServerHttpRequest; @@ -234,7 +235,7 @@ protected void handleTransportRequest(ServerHttpRequest request, ServerHttpRespo TransportType transportType = TransportType.fromValue(transport); if (transportType == null) { if (logger.isWarnEnabled()) { - logger.warn("Unknown transport type for " + request.getURI()); + logger.warn(LogFormatUtils.formatValue("Unknown transport type for " + request.getURI(), -1, true)); } response.setStatusCode(HttpStatus.NOT_FOUND); return; @@ -243,7 +244,7 @@ protected void handleTransportRequest(ServerHttpRequest request, ServerHttpRespo TransportHandler transportHandler = this.handlers.get(transportType); if (transportHandler == null) { if (logger.isWarnEnabled()) { - logger.warn("No TransportHandler for " + request.getURI()); + logger.warn(LogFormatUtils.formatValue("No TransportHandler for " + request.getURI(), -1, true)); } response.setStatusCode(HttpStatus.NOT_FOUND); return; diff --git a/src/docs/asciidoc/core/core-appendix.adoc b/src/docs/asciidoc/core/core-appendix.adoc index 53ef8e333583..095f767518a0 100644 --- a/src/docs/asciidoc/core/core-appendix.adoc +++ b/src/docs/asciidoc/core/core-appendix.adoc @@ -749,7 +749,7 @@ essentially the same as the following XML snippet: [source,xml,indent=0,subs="verbatim,quotes"] ---- - + ---- diff --git a/src/docs/asciidoc/core/core-beans.adoc b/src/docs/asciidoc/core/core-beans.adoc index 8d701befcbcc..72138867377e 100644 --- a/src/docs/asciidoc/core/core-beans.adoc +++ b/src/docs/asciidoc/core/core-beans.adoc @@ -2969,6 +2969,8 @@ When using annotation-driven components or Java configuration, you can use the ---- + + [[beans-factory-scopes-application]] ==== Application Scope @@ -3012,6 +3014,17 @@ following example shows how to do so: + +[[beans-factory-scopes-websocket]] +==== WebSocket Scope + +WebSocket scope is associated with the lifecycle of a WebSocket session and applies to +STOMP over WebSocket applications, see +<> for more details. + + + + [[beans-factory-scopes-other-injection]] ==== Scoped Beans as Dependencies @@ -7953,6 +7966,26 @@ following text image shows: transferService -> com.acme.TransferServiceImpl ---- +You can also use default methods to define beans. This allows composition of bean +configurations by implementing interfaces with bean definitions on default methods. + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + public interface BaseConfig { + + @Bean + default TransferServiceImpl transferService() { + return new TransferServiceImpl(); + } + } + + @Configuration + public class AppConfig implements BaseConfig { + + } +---- + You can also declare your `@Bean` method with an interface (or base class) return type, as the following example shows: diff --git a/src/docs/asciidoc/data-access.adoc b/src/docs/asciidoc/data-access.adoc index edf4ccbe5636..55d6be79646d 100644 --- a/src/docs/asciidoc/data-access.adoc +++ b/src/docs/asciidoc/data-access.adoc @@ -6788,20 +6788,20 @@ You can take control over result mapping by supplying a `Function` that called for each `Row` so it can can return arbitrary values (singular values, collections and maps, and objects). -The following example extracts the `id` column and emits its value: +The following example extracts the `name` column and emits its value: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- Flux names = client.sql("SELECT name FROM person") - .map(row -> row.get("id", String.class)) + .map(row -> row.get("name", String.class)) .all(); ---- [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- val names = client.sql("SELECT name FROM person") - .map{ row: Row -> row.get("id", String.class) } + .map{ row: Row -> row.get("name", String.class) } .flow() ---- diff --git a/src/docs/asciidoc/integration.adoc b/src/docs/asciidoc/integration.adoc index f2ec53ad0aed..9ea8f646d7d5 100644 --- a/src/docs/asciidoc/integration.adoc +++ b/src/docs/asciidoc/integration.adoc @@ -5396,6 +5396,7 @@ You can use these macros instead of the six-digit value, thus: `@Scheduled(cron |=== + [[scheduling-quartz]] === Using the Quartz Scheduler @@ -5451,7 +5452,6 @@ has it applied automatically: protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException { // do the actual work } - } ---- @@ -5571,11 +5571,19 @@ seconds and one running every morning at 6 AM. To finalize everything, we need t ---- -More properties are available for the `SchedulerFactoryBean`, such as the calendars -used by the job details, properties to customize Quartz with, and others. See the -{api-spring-framework}/scheduling/quartz/SchedulerFactoryBean.html[`SchedulerFactoryBean`] +More properties are available for the `SchedulerFactoryBean`, such as the calendars used by the +job details, properties to customize Quartz with, and a Spring-provided JDBC DataSource. See +the {api-spring-framework}/scheduling/quartz/SchedulerFactoryBean.html[`SchedulerFactoryBean`] javadoc for more information. +NOTE: `SchedulerFactoryBean` also recognizes a `quartz.properties` file in the classpath, +based on Quartz property keys, as with regular Quartz configuration. Please note that many +`SchedulerFactoryBean` settings interact with common Quartz settings in the properties file; +it is therefore not recommended to specify values at both levels. For example, do not set +an "org.quartz.jobStore.class" property if you mean to rely on a Spring-provided DataSource, +or specify an `org.springframework.scheduling.quartz.LocalDataSourceJobStore` variant which +is a full-fledged replacement for the standard `org.quartz.impl.jdbcjobstore.JobStoreTX`. + @@ -5877,7 +5885,6 @@ is updated in the cache. The following example shows how to use the `sync` attri ---- <1> Using the `sync` attribute. - NOTE: This is an optional feature, and your favorite cache library may not support it. All `CacheManager` implementations provided by the core framework support it. See the documentation of your cache provider for more details. @@ -6035,7 +6042,6 @@ all entries from the `books` cache: ---- <1> Using the `allEntries` attribute to evict all entries from the cache. - This option comes in handy when an entire cache region needs to be cleared out. Rather than evicting each entry (which would take a long time, since it is inefficient), all the entries are removed in one operation, as the preceding example shows. @@ -6094,7 +6100,6 @@ comes into play. The following examples uses `@CacheConfig` to set the name of t ---- <1> Using `@CacheConfig` to set the name of the cache. - `@CacheConfig` is a class-level annotation that allows sharing the cache names, the custom `KeyGenerator`, the custom `CacheManager`, and the custom `CacheResolver`. Placing this annotation on the class does not turn on any caching operation. @@ -6235,13 +6240,11 @@ if you need to annotate non-public methods, as it changes the bytecode itself. **** TIP: Spring recommends that you only annotate concrete classes (and methods of concrete -classes) with the `@Cache{asterisk}` annotation, as opposed to annotating interfaces. -You certainly can place the `@Cache{asterisk}` annotation on an interface (or an interface -method), but this works only as you would expect it to if you use interface-based proxies. -The fact that Java annotations are not inherited from interfaces means that, if you use -class-based proxies (`proxy-target-class="true"`) or the weaving-based aspect -(`mode="aspectj"`), the caching settings are not recognized by the proxying and weaving -infrastructure, and the object is not wrapped in a caching proxy. +classes) with the `@Cache{asterisk}` annotations, as opposed to annotating interfaces. +You certainly can place an `@Cache{asterisk}` annotation on an interface (or an interface +method), but this works only if you use the proxy mode (`mode="proxy"`). If you use the +weaving-based aspect (`mode="aspectj"`), the caching settings are not recognized on +interface-level declarations by the weaving infrastructure. NOTE: In proxy mode (the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation (in effect, a method within the @@ -6378,7 +6381,6 @@ to customize the factory for each cache operation, as the following example show ---- <1> Customizing the factory for this operation. - NOTE: For all referenced classes, Spring tries to locate a bean with the given type. If more than one match exists, a new instance is created and can use the regular bean lifecycle callbacks, such as dependency injection. diff --git a/src/docs/asciidoc/web/webflux-webclient.adoc b/src/docs/asciidoc/web/webflux-webclient.adoc index 3099ecb02d26..e93d1214046e 100644 --- a/src/docs/asciidoc/web/webflux-webclient.adoc +++ b/src/docs/asciidoc/web/webflux-webclient.adoc @@ -546,17 +546,13 @@ depending on the response status: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- - Mono entityMono = client.get() + Mono entityMono = client.get() .uri("/persons/1") .accept(MediaType.APPLICATION_JSON) .exchangeToMono(response -> { if (response.statusCode().equals(HttpStatus.OK)) { return response.bodyToMono(Person.class); } - else if (response.statusCode().is4xxClientError()) { - // Suppress error status code - return response.bodyToMono(ErrorContainer.class); - } else { // Turn to error return response.createException().flatMap(Mono::error); @@ -573,9 +569,6 @@ val entity = client.get() if (response.statusCode() == HttpStatus.OK) { return response.awaitBody() } - else if (response.statusCode().is4xxClientError) { - return response.awaitBody() - } else { throw response.createExceptionAndAwait() } diff --git a/src/docs/asciidoc/web/websocket.adoc b/src/docs/asciidoc/web/websocket.adoc index e4ee1e49c055..2a8042403d5c 100644 --- a/src/docs/asciidoc/web/websocket.adoc +++ b/src/docs/asciidoc/web/websocket.adoc @@ -1439,8 +1439,11 @@ See <>. If configured with a task scheduler, the simple broker supports https://stomp.github.io/stomp-specification-1.2.html#Heart-beating[STOMP heartbeats]. -For that, you can declare your own scheduler or use the one that is automatically -declared and used internally. The following example shows how to declare your own scheduler: +To configure a scheduler, you can declare your own `TaskScheduler` bean and set it through +the `MessageBrokerRegistry`. Alternatively, you can use the one that is automatically +declared in the built-in WebSocket configuration, however, you'll' need `@Lazy` to avoid +a cycle between the built-in WebSocket configuration and your +`WebSocketMessageBrokerConfigurer`. For example: [source,java,indent=0,subs="verbatim,quotes"] ---- @@ -1451,13 +1454,12 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { private TaskScheduler messageBrokerTaskScheduler; @Autowired - public void setMessageBrokerTaskScheduler(TaskScheduler taskScheduler) { + public void setMessageBrokerTaskScheduler(@Lazy TaskScheduler taskScheduler) { this.messageBrokerTaskScheduler = taskScheduler; } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { - registry.enableSimpleBroker("/queue/", "/topic/") .setHeartbeatValue(new long[] {10000, 20000}) .setTaskScheduler(this.messageBrokerTaskScheduler); pFad - Phonifier reborn

    Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

    Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


    Alternative Proxies:

    Alternative Proxy

    pFad Proxy

    pFad v3 Proxy

    pFad v4 Proxy