diff --git a/build.gradle b/build.gradle
index d21c458e1fcb..dc4cc1da1dcb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,10 +29,10 @@ configure(allprojects) { project ->
imports {
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.6"
mavenBom "io.netty:netty-bom:4.1.75.Final"
- mavenBom "io.projectreactor:reactor-bom:2020.0.17"
- mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR12"
- mavenBom "io.rsocket:rsocket-bom:1.1.1"
- mavenBom "org.eclipse.jetty:jetty-bom:9.4.45.v20220203"
+ mavenBom "io.projectreactor:reactor-bom:2020.0.18"
+ mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR13"
+ mavenBom "io.rsocket:rsocket-bom:1.1.2"
+ mavenBom "org.eclipse.jetty:jetty-bom:9.4.46.v20220331"
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"
@@ -96,7 +96,7 @@ configure(allprojects) { project ->
dependency "com.h2database:h2:2.1.210"
dependency "com.github.ben-manes.caffeine:caffeine:2.9.3"
- dependency "com.github.librepdf:openpdf:1.3.26"
+ dependency "com.github.librepdf:openpdf:1.3.27"
dependency "com.rometools:rome:1.18.0"
dependency "commons-io:commons-io:2.5"
dependency "io.vavr:vavr:0.10.4"
@@ -128,18 +128,18 @@ 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.60') {
+ dependencySet(group: 'org.apache.tomcat', version: '9.0.62') {
entry 'tomcat-util'
entry('tomcat-websocket') {
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.60') {
+ dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.62') {
entry 'tomcat-embed-core'
entry 'tomcat-embed-websocket'
}
- dependencySet(group: 'io.undertow', version: '2.2.16.Final') {
+ dependencySet(group: 'io.undertow', version: '2.2.17.Final') {
entry 'undertow-core'
entry('undertow-servlet') {
exclude group: "org.jboss.spec.javax.servlet", name: "jboss-servlet-api_4.0_spec"
@@ -150,7 +150,7 @@ configure(allprojects) { project ->
}
}
- dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.10"
+ dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.11"
dependency 'org.apache.httpcomponents.client5:httpclient5:5.1.3'
dependency 'org.apache.httpcomponents.core5:httpcore5-reactive:5.1.3'
dependency("org.apache.httpcomponents:httpclient:4.5.13") {
@@ -206,10 +206,10 @@ configure(allprojects) { project ->
}
dependency "io.mockk:mockk:1.12.1"
- dependency("net.sourceforge.htmlunit:htmlunit:2.59.0") {
+ dependency("net.sourceforge.htmlunit:htmlunit:2.60.0") {
exclude group: "commons-logging", name: "commons-logging"
}
- dependency("org.seleniumhq.selenium:htmlunit-driver:2.59.0") {
+ dependency("org.seleniumhq.selenium:htmlunit-driver:2.60.0") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.seleniumhq.selenium:selenium-java:3.141.59") {
@@ -340,7 +340,7 @@ configure([rootProject] + javaProjects) { project ->
}
checkstyle {
- toolVersion = "9.3"
+ toolVersion = "10.1"
configDirectory.set(rootProject.file("src/checkstyle"))
}
diff --git a/ci/images/ci-image/Dockerfile b/ci/images/ci-image/Dockerfile
index ac4db911a85f..37ab9b6edb47 100644
--- a/ci/images/ci-image/Dockerfile
+++ b/ci/images/ci-image/Dockerfile
@@ -1,4 +1,4 @@
-FROM ubuntu:focal-20220302
+FROM ubuntu:focal-20220404
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
diff --git a/ci/images/get-jdk-url.sh b/ci/images/get-jdk-url.sh
index d090494f6627..3b0006dd7f6d 100755
--- a/ci/images/get-jdk-url.sh
+++ b/ci/images/get-jdk-url.sh
@@ -12,7 +12,7 @@ case "$1" in
echo "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.2%2B8/OpenJDK17U-jdk_x64_linux_hotspot_17.0.2_8.tar.gz"
;;
java18)
- echo "https://github.com/adoptium/temurin18-binaries/releases/download/jdk18-2022-02-12-08-06-beta/OpenJDK18-jdk_x64_linux_hotspot_2022-02-12-08-06.tar.gz"
+ echo "https://github.com/adoptium/temurin18-binaries/releases/download/jdk-18%2B36/OpenJDK18U-jdk_x64_linux_hotspot_18_36.tar.gz"
;;
*)
echo $"Unknown java version"
diff --git a/ci/pipeline.yml b/ci/pipeline.yml
index eb8de81ea1e1..712bd2cac0de 100644
--- a/ci/pipeline.yml
+++ b/ci/pipeline.yml
@@ -41,6 +41,11 @@ anchors:
GITHUB_TOKEN: ((github-ci-release-token))
resource_types:
+- name: registry-image
+ type: registry-image
+ source:
+ repository: concourse/registry-image-resource
+ tag: 1.5.0
- name: artifactory-resource
type: registry-image
source:
diff --git a/gradle.properties b/gradle.properties
index 75c9b894bcfa..00985a0fbeed 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,4 @@
-version=5.3.18-SNAPSHOT
+version=5.3.19
org.gradle.jvmargs=-Xmx1536M
org.gradle.caching=true
org.gradle.parallel=true
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java b/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java
index f6f7bd7acfd6..08274272386a 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java
@@ -134,7 +134,7 @@ static Class>[] completeProxiedInterfaces(AdvisedSupport advised, boolean deco
if (targetClass.isInterface()) {
advised.setInterfaces(targetClass);
}
- else if (Proxy.isProxyClass(targetClass) || isLambda(targetClass)) {
+ else if (Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
advised.setInterfaces(targetClass.getInterfaces());
}
specifiedInterfaces = advised.getProxiedInterfaces();
@@ -245,18 +245,4 @@ static Object[] adaptArgumentsIfNecessary(Method method, @Nullable Object[] argu
return arguments;
}
- /**
- * Determine if the supplied {@link Class} is a JVM-generated implementation
- * class for a lambda expression or method reference.
- *
This method makes a best-effort attempt at determining this, based on
- * checks that work on modern, main stream JVMs.
- * @param clazz the class to check
- * @return {@code true} if the class is a lambda implementation class
- * @since 5.3.16
- */
- static boolean isLambda(Class> clazz) {
- return (clazz.isSynthetic() && (clazz.getSuperclass() == Object.class) &&
- (clazz.getInterfaces().length > 0) && clazz.getName().contains("$$Lambda"));
- }
-
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java b/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java
index 022cc0fddf24..87fa84d6b98a 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -375,6 +375,22 @@ private static boolean implementsInterface(Method method, Set> ifcs) {
return false;
}
+ /**
+ * Invoke the given method with a CGLIB MethodProxy if possible, falling back
+ * to a plain reflection invocation in case of a fast-class generation failure.
+ */
+ @Nullable
+ private static Object invokeMethod(@Nullable Object target, Method method, Object[] args, MethodProxy methodProxy)
+ throws Throwable {
+ try {
+ return methodProxy.invoke(target, args);
+ }
+ catch (CodeGenerationException ex) {
+ CglibMethodInvocation.logFastClassGenerationFailure(method);
+ return AopUtils.invokeJoinpointUsingReflection(target, method, args);
+ }
+ }
+
/**
* Process a return value. Wraps a return of {@code this} if necessary to be the
* {@code proxy} and also verifies that {@code null} is not returned as a primitive.
@@ -425,7 +441,7 @@ public StaticUnadvisedInterceptor(@Nullable Object target) {
@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
- Object retVal = methodProxy.invoke(this.target, args);
+ Object retVal = invokeMethod(this.target, method, args, methodProxy);
return processReturnType(proxy, this.target, method, retVal);
}
}
@@ -450,7 +466,7 @@ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy
Object oldProxy = null;
try {
oldProxy = AopContext.setCurrentProxy(proxy);
- Object retVal = methodProxy.invoke(this.target, args);
+ Object retVal = invokeMethod(this.target, method, args, methodProxy);
return processReturnType(proxy, this.target, method, retVal);
}
finally {
@@ -478,7 +494,7 @@ public DynamicUnadvisedInterceptor(TargetSource targetSource) {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object target = this.targetSource.getTarget();
try {
- Object retVal = methodProxy.invoke(target, args);
+ Object retVal = invokeMethod(target, method, args, methodProxy);
return processReturnType(proxy, target, method, retVal);
}
finally {
@@ -508,7 +524,7 @@ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy
Object target = this.targetSource.getTarget();
try {
oldProxy = AopContext.setCurrentProxy(proxy);
- Object retVal = methodProxy.invoke(target, args);
+ Object retVal = invokeMethod(target, method, args, methodProxy);
return processReturnType(proxy, target, method, retVal);
}
finally {
@@ -685,13 +701,7 @@ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
- try {
- retVal = methodProxy.invoke(target, argsToUse);
- }
- catch (CodeGenerationException ex) {
- CglibMethodInvocation.logFastClassGenerationFailure(method);
- retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
- }
+ retVal = invokeMethod(target, method, argsToUse, methodProxy);
}
else {
// We need to create a method invocation...
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 5f1acad9a9a2..e63e17212322 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
@@ -21,6 +21,7 @@
import org.springframework.aop.SpringProxy;
import org.springframework.core.NativeDetector;
+import org.springframework.util.ClassUtils;
/**
* Default {@link AopProxyFactory} implementation, creating either a CGLIB proxy
@@ -60,7 +61,7 @@ public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
- if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || AopProxyUtils.isLambda(targetClass)) {
+ if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java
index 3e68f820ecb9..c550168800e4 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,6 +50,7 @@
import org.springframework.core.SmartClassLoader;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
@@ -85,6 +86,7 @@
* @author Juergen Hoeller
* @author Rod Johnson
* @author Rob Harrop
+ * @author Sam Brannen
* @since 13.10.2003
* @see #setInterceptorNames
* @see #getAdvicesAndAdvisorsForBean
@@ -442,8 +444,8 @@ protected Object createProxy(Class> beanClass, @Nullable String beanName,
proxyFactory.copyFrom(this);
if (proxyFactory.isProxyTargetClass()) {
- // Explicit handling of JDK proxy targets (for introduction advice scenarios)
- if (Proxy.isProxyClass(beanClass)) {
+ // Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios)
+ if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {
// Must allow for introductions; can't just set interfaces to the proxy's interfaces only.
for (Class> ifc : beanClass.getInterfaces()) {
proxyFactory.addInterface(ifc);
diff --git a/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java b/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java
index 3dbf550a1211..2704cf1c7c2f 100644
--- a/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java
+++ b/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java
@@ -19,7 +19,6 @@
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.List;
-import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
@@ -134,61 +133,4 @@ public void testProxiedUserInterfacesWithNoInterface() {
AopProxyUtils.proxiedUserInterfaces(proxy));
}
- @Test
- void isLambda() {
- assertIsLambda(AopProxyUtilsTests.staticLambdaExpression);
- assertIsLambda(AopProxyUtilsTests::staticStringFactory);
-
- assertIsLambda(this.instanceLambdaExpression);
- assertIsLambda(this::instanceStringFactory);
- }
-
- @Test
- void isNotLambda() {
- assertIsNotLambda(new EnigmaSupplier());
-
- assertIsNotLambda(new Supplier() {
- @Override
- public String get() {
- return "anonymous inner class";
- }
- });
-
- assertIsNotLambda(new Fake$$LambdaSupplier());
- }
-
- private static void assertIsLambda(Supplier supplier) {
- assertThat(AopProxyUtils.isLambda(supplier.getClass())).isTrue();
- }
-
- private static void assertIsNotLambda(Supplier supplier) {
- assertThat(AopProxyUtils.isLambda(supplier.getClass())).isFalse();
- }
-
- private static final Supplier staticLambdaExpression = () -> "static lambda expression";
-
- private final Supplier instanceLambdaExpression = () -> "instance lambda expressions";
-
- private static String staticStringFactory() {
- return "static string factory";
- }
-
- private String instanceStringFactory() {
- return "instance string factory";
- }
-
- private static class EnigmaSupplier implements Supplier {
- @Override
- public String get() {
- return "enigma";
- }
- }
-
- private static class Fake$$LambdaSupplier implements Supplier {
- @Override
- public String get() {
- return "fake lambda";
- }
- }
-
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java
index 4187097ce371..8332045197de 100644
--- a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java
+++ b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java
@@ -287,13 +287,15 @@ private CachedIntrospectionResults(Class> beanClass) throws BeansException {
// This call is slow so we do it once.
PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
- if (Class.class == beanClass && (!"name".equals(pd.getName()) && !pd.getName().endsWith("Name"))) {
+ if (Class.class == beanClass && !("name".equals(pd.getName()) ||
+ (pd.getName().endsWith("Name") && String.class == pd.getPropertyType()))) {
// Only allow all name variants of Class properties
continue;
}
- if (pd.getPropertyType() != null && (ClassLoader.class.isAssignableFrom(pd.getPropertyType())
- || ProtectionDomain.class.isAssignableFrom(pd.getPropertyType()))) {
- // Ignore ClassLoader and ProtectionDomain types - nobody needs to bind to those
+ if (pd.getWriteMethod() == null && pd.getPropertyType() != null &&
+ (ClassLoader.class.isAssignableFrom(pd.getPropertyType()) ||
+ ProtectionDomain.class.isAssignableFrom(pd.getPropertyType()))) {
+ // Ignore ClassLoader and ProtectionDomain read-only properties - no need to bind to those
continue;
}
if (logger.isTraceEnabled()) {
@@ -342,9 +344,10 @@ private void introspectInterfaces(Class> beanClass, Class> currClass, Set beanClass, Set readMethod
for (Method method : beanClass.getMethods()) {
if (!this.propertyDescriptors.containsKey(method.getName()) &&
- !readMethodNames.contains((method.getName())) && isPlainAccessor(method)) {
+ !readMethodNames.contains(method.getName()) && isPlainAccessor(method)) {
this.propertyDescriptors.put(method.getName(),
new GenericTypeAwarePropertyDescriptor(beanClass, method.getName(), method, null, null));
readMethodNames.add(method.getName());
@@ -373,8 +376,11 @@ private void introspectPlainAccessors(Class> beanClass, Set readMethod
}
private boolean isPlainAccessor(Method method) {
- if (method.getParameterCount() > 0 || method.getReturnType() == void.class ||
- method.getDeclaringClass() == Object.class || Modifier.isStatic(method.getModifiers())) {
+ if (Modifier.isStatic(method.getModifiers()) ||
+ method.getDeclaringClass() == Object.class || method.getDeclaringClass() == Class.class ||
+ method.getParameterCount() > 0 || method.getReturnType() == void.class ||
+ ClassLoader.class.isAssignableFrom(method.getReturnType()) ||
+ ProtectionDomain.class.isAssignableFrom(method.getReturnType())) {
return false;
}
try {
diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyAccessor.java b/spring-beans/src/main/java/org/springframework/beans/PropertyAccessor.java
index 3a417aadcd37..03201a89d0d7 100644
--- a/spring-beans/src/main/java/org/springframework/beans/PropertyAccessor.java
+++ b/spring-beans/src/main/java/org/springframework/beans/PropertyAccessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,8 +23,9 @@
/**
* Common interface for classes that can access named properties
- * (such as bean properties of an object or fields in an object)
- * Serves as base interface for {@link BeanWrapper}.
+ * (such as bean properties of an object or fields in an object).
+ *
+ * Serves as base interface for {@link BeanWrapper}.
*
* @author Juergen Hoeller
* @since 1.1
diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java
index 8856e31a0e05..ab154ea3c4e6 100644
--- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,8 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.testfixture.beans.TestBean;
+import org.springframework.core.OverridingClassLoader;
+import org.springframework.core.io.DefaultResourceLoader;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -105,7 +107,7 @@ void checkNotWritablePropertyHoldPossibleMatches() {
.satisfies(ex -> assertThat(ex.getPossibleMatches()).containsExactly("age"));
}
- @Test // Can't be shared; there is no such thing as a read-only field
+ @Test // Can't be shared; there is no such thing as a read-only field
void setReadOnlyMapProperty() {
TypedReadOnlyMap map = new TypedReadOnlyMap(Collections.singletonMap("key", new TestBean()));
TypedReadOnlyMapClient target = new TypedReadOnlyMapClient();
@@ -157,12 +159,34 @@ void propertyDescriptors() {
BeanWrapper accessor = createAccessor(target);
accessor.setPropertyValue("name", "a");
accessor.setPropertyValue("spouse.name", "b");
+
assertThat(target.getName()).isEqualTo("a");
assertThat(target.getSpouse().getName()).isEqualTo("b");
assertThat(accessor.getPropertyValue("name")).isEqualTo("a");
assertThat(accessor.getPropertyValue("spouse.name")).isEqualTo("b");
assertThat(accessor.getPropertyDescriptor("name").getPropertyType()).isEqualTo(String.class);
assertThat(accessor.getPropertyDescriptor("spouse.name").getPropertyType()).isEqualTo(String.class);
+
+ assertThat(accessor.isReadableProperty("class.package")).isFalse();
+ assertThat(accessor.isReadableProperty("class.module")).isFalse();
+ assertThat(accessor.isReadableProperty("class.classLoader")).isFalse();
+ assertThat(accessor.isReadableProperty("class.name")).isTrue();
+ assertThat(accessor.isReadableProperty("class.simpleName")).isTrue();
+ assertThat(accessor.getPropertyValue("class.name")).isEqualTo(TestBean.class.getName());
+ assertThat(accessor.getPropertyValue("class.simpleName")).isEqualTo(TestBean.class.getSimpleName());
+ assertThat(accessor.getPropertyDescriptor("class.name").getPropertyType()).isEqualTo(String.class);
+ assertThat(accessor.getPropertyDescriptor("class.simpleName").getPropertyType()).isEqualTo(String.class);
+
+ accessor = createAccessor(new DefaultResourceLoader());
+
+ assertThat(accessor.isReadableProperty("class.package")).isFalse();
+ assertThat(accessor.isReadableProperty("class.module")).isFalse();
+ assertThat(accessor.isReadableProperty("class.classLoader")).isFalse();
+ assertThat(accessor.isReadableProperty("classLoader")).isTrue();
+ assertThat(accessor.isWritableProperty("classLoader")).isTrue();
+ OverridingClassLoader ocl = new OverridingClassLoader(getClass().getClassLoader());
+ accessor.setPropertyValue("classLoader", ocl);
+ assertThat(accessor.getPropertyValue("classLoader")).isSameAs(ocl);
}
@Test
diff --git a/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/ValidatorFactoryTests.java b/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/ValidatorFactoryTests.java
index a383ae5f8bf7..ea2fb76ae882 100644
--- a/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/ValidatorFactoryTests.java
+++ b/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/ValidatorFactoryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
+import javax.validation.ConstraintValidatorFactory;
import javax.validation.ConstraintViolation;
import javax.validation.Payload;
import javax.validation.Valid;
@@ -43,6 +44,7 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.convert.support.DefaultConversionService;
@@ -52,18 +54,18 @@
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
+import org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Juergen Hoeller
*/
-@SuppressWarnings("resource")
-public class ValidatorFactoryTests {
+class ValidatorFactoryTests {
@Test
- @SuppressWarnings("cast")
- public void testSimpleValidation() {
+ void simpleValidation() {
+ @SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
@@ -78,15 +80,15 @@ public void testSimpleValidation() {
Validator nativeValidator = validator.unwrap(Validator.class);
assertThat(nativeValidator.getClass().getName().startsWith("org.hibernate")).isTrue();
- assertThat(validator.unwrap(ValidatorFactory.class) instanceof HibernateValidatorFactory).isTrue();
- assertThat(validator.unwrap(HibernateValidatorFactory.class) instanceof HibernateValidatorFactory).isTrue();
+ assertThat(validator.unwrap(ValidatorFactory.class)).isInstanceOf(HibernateValidatorFactory.class);
+ assertThat(validator.unwrap(HibernateValidatorFactory.class)).isInstanceOf(HibernateValidatorFactory.class);
validator.destroy();
}
@Test
- @SuppressWarnings("cast")
- public void testSimpleValidationWithCustomProvider() {
+ void simpleValidationWithCustomProvider() {
+ @SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setProviderClass(HibernateValidator.class);
validator.afterPropertiesSet();
@@ -102,14 +104,15 @@ public void testSimpleValidationWithCustomProvider() {
Validator nativeValidator = validator.unwrap(Validator.class);
assertThat(nativeValidator.getClass().getName().startsWith("org.hibernate")).isTrue();
- assertThat(validator.unwrap(ValidatorFactory.class) instanceof HibernateValidatorFactory).isTrue();
- assertThat(validator.unwrap(HibernateValidatorFactory.class) instanceof HibernateValidatorFactory).isTrue();
+ assertThat(validator.unwrap(ValidatorFactory.class)).isInstanceOf(HibernateValidatorFactory.class);
+ assertThat(validator.unwrap(HibernateValidatorFactory.class)).isInstanceOf(HibernateValidatorFactory.class);
validator.destroy();
}
@Test
- public void testSimpleValidationWithClassLevel() {
+ void simpleValidationWithClassLevel() {
+ @SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
@@ -122,10 +125,13 @@ public void testSimpleValidationWithClassLevel() {
ConstraintViolation> cv = iterator.next();
assertThat(cv.getPropertyPath().toString()).isEqualTo("");
assertThat(cv.getConstraintDescriptor().getAnnotation() instanceof NameAddressValid).isTrue();
+
+ validator.destroy();
}
@Test
- public void testSpringValidationFieldType() {
+ void springValidationFieldType() {
+ @SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
@@ -135,11 +141,16 @@ public void testSpringValidationFieldType() {
BeanPropertyBindingResult errors = new BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
assertThat(errors.getErrorCount()).isEqualTo(1);
- assertThat(errors.getFieldError("address").getRejectedValue()).isInstanceOf(ValidAddress.class);
+ assertThat(errors.getFieldError("address").getRejectedValue())
+ .as("Field/Value type mismatch")
+ .isInstanceOf(ValidAddress.class);
+
+ validator.destroy();
}
@Test
- public void testSpringValidation() {
+ void springValidation() {
+ @SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
@@ -164,10 +175,13 @@ public void testSpringValidation() {
assertThat(errorCodes.contains("NotNull.street")).isTrue();
assertThat(errorCodes.contains("NotNull.java.lang.String")).isTrue();
assertThat(errorCodes.contains("NotNull")).isTrue();
+
+ validator.destroy();
}
@Test
- public void testSpringValidationWithClassLevel() {
+ void springValidationWithClassLevel() {
+ @SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
@@ -182,10 +196,12 @@ public void testSpringValidationWithClassLevel() {
assertThat(errorCodes.size()).isEqualTo(2);
assertThat(errorCodes.contains("NameAddressValid.person")).isTrue();
assertThat(errorCodes.contains("NameAddressValid")).isTrue();
+
+ validator.destroy();
}
@Test
- public void testSpringValidationWithAutowiredValidator() {
+ void springValidationWithAutowiredValidator() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(
LocalValidatorFactoryBean.class);
LocalValidatorFactoryBean validator = ctx.getBean(LocalValidatorFactoryBean.class);
@@ -202,11 +218,14 @@ public void testSpringValidationWithAutowiredValidator() {
assertThat(errorCodes.size()).isEqualTo(2);
assertThat(errorCodes.contains("NameAddressValid.person")).isTrue();
assertThat(errorCodes.contains("NameAddressValid")).isTrue();
+
+ validator.destroy();
ctx.close();
}
@Test
- public void testSpringValidationWithErrorInListElement() {
+ void springValidationWithErrorInListElement() {
+ @SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
@@ -221,10 +240,13 @@ public void testSpringValidationWithErrorInListElement() {
assertThat(fieldError.getField()).isEqualTo("address.street");
fieldError = result.getFieldError("addressList[0].street");
assertThat(fieldError.getField()).isEqualTo("addressList[0].street");
+
+ validator.destroy();
}
@Test
- public void testSpringValidationWithErrorInSetElement() {
+ void springValidationWithErrorInSetElement() {
+ @SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
@@ -239,10 +261,13 @@ public void testSpringValidationWithErrorInSetElement() {
assertThat(fieldError.getField()).isEqualTo("address.street");
fieldError = result.getFieldError("addressSet[].street");
assertThat(fieldError.getField()).isEqualTo("addressSet[].street");
+
+ validator.destroy();
}
@Test
- public void testInnerBeanValidation() {
+ void innerBeanValidation() {
+ @SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
@@ -251,10 +276,13 @@ public void testInnerBeanValidation() {
validator.validate(mainBean, errors);
Object rejected = errors.getFieldValue("inner.value");
assertThat(rejected).isNull();
+
+ validator.destroy();
}
@Test
- public void testValidationWithOptionalField() {
+ void validationWithOptionalField() {
+ @SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
@@ -263,10 +291,13 @@ public void testValidationWithOptionalField() {
validator.validate(mainBean, errors);
Object rejected = errors.getFieldValue("inner.value");
assertThat(rejected).isNull();
+
+ validator.destroy();
}
@Test
- public void testListValidation() {
+ void listValidation() {
+ @SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
@@ -282,6 +313,34 @@ public void testListValidation() {
assertThat(fieldError).isNotNull();
assertThat(fieldError.getRejectedValue()).isEqualTo("X");
assertThat(errors.getFieldValue("list[1]")).isEqualTo("X");
+
+ validator.destroy();
+ }
+
+ @Test
+ void withConstraintValidatorFactory() {
+ ConstraintValidatorFactory cvf = new SpringConstraintValidatorFactory(new DefaultListableBeanFactory());
+
+ @SuppressWarnings("resource")
+ LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
+ validator.setConstraintValidatorFactory(cvf);
+ validator.afterPropertiesSet();
+
+ assertThat(validator.getConstraintValidatorFactory()).isSameAs(cvf);
+ validator.destroy();
+ }
+
+ @Test
+ void withCustomInitializer() {
+ ConstraintValidatorFactory cvf = new SpringConstraintValidatorFactory(new DefaultListableBeanFactory());
+
+ @SuppressWarnings("resource")
+ LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
+ validator.setConfigurationInitializer(configuration -> configuration.constraintValidatorFactory(cvf));
+ validator.afterPropertiesSet();
+
+ assertThat(validator.getConstraintValidatorFactory()).isSameAs(cvf);
+ validator.destroy();
}
@@ -380,8 +439,8 @@ public boolean isValid(ValidPerson value, ConstraintValidatorContext context) {
}
boolean valid = (value.name == null || !value.address.street.contains(value.name));
if (!valid && "Phil".equals(value.name)) {
- context.buildConstraintViolationWithTemplate(
- context.getDefaultConstraintMessageTemplate()).addPropertyNode("address").addConstraintViolation().disableDefaultConstraintViolation();
+ context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
+ .addPropertyNode("address").addConstraintViolation().disableDefaultConstraintViolation();
}
return valid;
}
@@ -417,6 +476,7 @@ public static class InnerBean {
public String getValue() {
return value;
}
+
public void setValue(String value) {
this.value = value;
}
@@ -425,8 +485,8 @@ public void setValue(String value) {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
- @Constraint(validatedBy=InnerValidator.class)
- public static @interface InnerValid {
+ @Constraint(validatedBy = InnerValidator.class)
+ public @interface InnerValid {
String message() default "NOT VALID";
@@ -446,7 +506,8 @@ public void initialize(InnerValid constraintAnnotation) {
public boolean isValid(InnerBean bean, ConstraintValidatorContext context) {
context.disableDefaultConstraintViolation();
if (bean.getValue() == null) {
- context.buildConstraintViolationWithTemplate("NULL").addPropertyNode("value").addConstraintViolation();
+ context.buildConstraintViolationWithTemplate("NULL")
+ .addPropertyNode("value").addConstraintViolation();
return false;
}
return true;
@@ -494,7 +555,8 @@ public boolean isValid(List list, ConstraintValidatorContext context) {
boolean valid = true;
for (int i = 0; i < list.size(); i++) {
if ("X".equals(list.get(i))) {
- context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()).addBeanNode().inIterable().atIndex(i).addConstraintViolation();
+ context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
+ .addBeanNode().inIterable().atIndex(i).addConstraintViolation();
valid = false;
}
}
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 612dfc5622a2..8ee5e43b02fe 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-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,18 +51,20 @@
import org.springframework.util.StringUtils;
/**
- * Binder that allows for setting property values onto a target object,
- * including support for validation and binding result analysis.
- * The binding process can be customized through specifying allowed fields,
+ * Binder that allows for setting property values on a target object, including
+ * support for validation and binding result analysis.
+ *
+ * The binding process can be customized by specifying allowed field patterns,
* required fields, custom editors, etc.
*
- *
Note that there are potential security implications in failing to set an array
- * of allowed fields. In the case of HTTP form POST data for example, malicious clients
- * can attempt to subvert an application by supplying values for fields or properties
- * that do not exist on the form. In some cases this could lead to illegal data being
- * set on command objects or their nested objects. For this reason, it is
- * highly recommended to specify the {@link #setAllowedFields allowedFields} property
- * on the DataBinder.
+ *
WARNING: Data binding can lead to security issues by exposing
+ * parts of the object graph that are not meant to be accessed or modified by
+ * external clients. Therefore the design and use of data binding should be considered
+ * carefully with regard to security. For more details, please refer to the dedicated
+ * sections on data binding for
+ * Spring Web MVC and
+ * Spring WebFlux
+ * in the reference manual.
*
*
The binding results can be examined via the {@link BindingResult} interface,
* extending the {@link Errors} interface: see the {@link #getBindingResult()} method.
@@ -96,6 +98,7 @@
* @author Rob Harrop
* @author Stephane Nicoll
* @author Kazuki Shimizu
+ * @author Sam Brannen
* @see #setAllowedFields
* @see #setRequiredFields
* @see #registerCustomEditor
@@ -418,15 +421,21 @@ 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
+ * Register field patterns 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
+ *
Supports {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
+ * {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
+ * well as direct equality.
+ *
The default implementation of this method stores allowed field patterns
+ * in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) canonical}
+ * form. Subclasses which override this method must therefore take this into
+ * account.
+ *
More sophisticated matching can be implemented by overriding the
+ * {@link #isAllowed} method.
+ *
Alternatively, specify a list of disallowed field patterns.
+ * @param allowedFields array of allowed field patterns
* @see #setDisallowedFields
* @see #isAllowed(String)
*/
@@ -435,8 +444,9 @@ public void setAllowedFields(@Nullable String... allowedFields) {
}
/**
- * Return the fields that should be allowed for binding.
- * @return array of field names
+ * Return the field patterns that should be allowed for binding.
+ * @return array of allowed field patterns
+ * @see #setAllowedFields(String...)
*/
@Nullable
public String[] getAllowedFields() {
@@ -444,25 +454,44 @@ public String[] getAllowedFields() {
}
/**
- * Register fields that should not be allowed for binding. Default
- * is none. Mark fields as disallowed for example to avoid unwanted
+ * Register field patterns 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
+ *
Supports {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
+ * {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
+ * well as direct equality.
+ *
The default implementation of this method stores disallowed field patterns
+ * in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) canonical}
+ * form. As of Spring Framework 5.2.21, the default implementation also transforms
+ * disallowed field patterns to {@linkplain String#toLowerCase() lowercase} to
+ * support case-insensitive pattern matching in {@link #isAllowed}. Subclasses
+ * which override this method must therefore take both of these transformations
+ * into account.
+ *
More sophisticated matching can be implemented by overriding the
+ * {@link #isAllowed} method.
+ *
Alternatively, specify a list of allowed field patterns.
+ * @param disallowedFields array of disallowed field patterns
* @see #setAllowedFields
* @see #isAllowed(String)
*/
public void setDisallowedFields(@Nullable String... disallowedFields) {
- this.disallowedFields = PropertyAccessorUtils.canonicalPropertyNames(disallowedFields);
+ if (disallowedFields == null) {
+ this.disallowedFields = null;
+ }
+ else {
+ String[] fieldPatterns = new String[disallowedFields.length];
+ for (int i = 0; i < fieldPatterns.length; i++) {
+ fieldPatterns[i] = PropertyAccessorUtils.canonicalPropertyName(disallowedFields[i]).toLowerCase();
+ }
+ this.disallowedFields = fieldPatterns;
+ }
}
/**
- * Return the fields that should not be allowed for binding.
- * @return array of field names
+ * Return the field patterns that should not be allowed for binding.
+ * @return array of disallowed field patterns
+ * @see #setDisallowedFields(String...)
*/
@Nullable
public String[] getDisallowedFields() {
@@ -774,16 +803,20 @@ 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", "*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.
+ * Determine if the given field is allowed for binding.
+ *
Invoked for each passed-in property value.
+ *
Checks for {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
+ * {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
+ * well as direct equality, in the configured lists of allowed field patterns
+ * and disallowed field patterns.
+ *
Matching against allowed field patterns is case-sensitive; whereas,
+ * matching against disallowed field patterns is case-insensitive.
+ *
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, but care must be taken to honor the
+ * aforementioned contract.
* @param field the field to check
- * @return if the field is allowed
+ * @return {@code true} if the field is allowed
* @see #setAllowedFields
* @see #setDisallowedFields
* @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String)
@@ -792,7 +825,7 @@ protected boolean isAllowed(String field) {
String[] allowed = getAllowedFields();
String[] disallowed = getDisallowedFields();
return ((ObjectUtils.isEmpty(allowed) || PatternMatchUtils.simpleMatch(allowed, field)) &&
- (ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field)));
+ (ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field.toLowerCase())));
}
/**
diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java
index 73ec646bead7..25fc0727474a 100644
--- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java
+++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
import java.util.List;
import java.util.Map;
import java.util.Properties;
+import java.util.function.Consumer;
import javax.validation.Configuration;
import javax.validation.ConstraintValidatorFactory;
@@ -113,6 +114,9 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
private final Map validationPropertyMap = new HashMap<>();
+ @Nullable
+ private Consumer> configurationInitializer;
+
@Nullable
private ApplicationContext applicationContext;
@@ -234,6 +238,18 @@ public Map getValidationPropertyMap() {
return this.validationPropertyMap;
}
+ /**
+ * Specify a callback for customizing the Bean Validation {@code Configuration} instance,
+ * as an alternative to overriding the {@link #postProcessConfiguration(Configuration)}
+ * method in custom {@code LocalValidatorFactoryBean} subclasses.
+ * This enables convenient customizations for application purposes. Infrastructure
+ * extensions may keep overriding the {@link #postProcessConfiguration} template method.
+ * @since 5.3.19
+ */
+ public void setConfigurationInitializer(Consumer> configurationInitializer) {
+ this.configurationInitializer = configurationInitializer;
+ }
+
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
@@ -312,6 +328,9 @@ public void afterPropertiesSet() {
this.validationPropertyMap.forEach(configuration::addProperty);
// Allow for custom post-processing before we actually build the ValidatorFactory.
+ if (this.configurationInitializer != null) {
+ this.configurationInitializer.accept(configuration);
+ }
postProcessConfiguration(configuration);
try {
diff --git a/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java b/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java
index 7c017cfa1aad..c506e210636f 100644
--- a/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java
+++ b/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java
@@ -21,6 +21,8 @@
import java.lang.reflect.Method;
import java.util.function.Supplier;
+import org.aopalliance.aop.Advice;
+import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
@@ -31,11 +33,17 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.aop.ClassFilter;
+import org.springframework.aop.IntroductionAdvisor;
+import org.springframework.aop.IntroductionInterceptor;
import org.springframework.aop.MethodBeforeAdvice;
+import org.springframework.aop.SpringProxy;
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.aop.aspectj.annotation.AspectMetadata;
import org.springframework.aop.config.AopConfigUtils;
+import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyConfig;
+import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.beans.PropertyValue;
@@ -52,6 +60,7 @@
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.core.DecoratingProxy;
import org.springframework.core.NestedRuntimeException;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
@@ -304,10 +313,26 @@ public void testWithBeanNameAutoProxyCreator() {
@ValueSource(classes = {ProxyTargetClassFalseConfig.class, ProxyTargetClassTrueConfig.class})
void lambdaIsAlwaysProxiedWithJdkProxy(Class> configClass) {
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(configClass)) {
- Supplier> supplier = context.getBean(Supplier.class);
+ @SuppressWarnings("unchecked")
+ Supplier supplier = context.getBean(Supplier.class);
assertThat(AopUtils.isAopProxy(supplier)).as("AOP proxy").isTrue();
assertThat(AopUtils.isJdkDynamicProxy(supplier)).as("JDK Dynamic proxy").isTrue();
- assertThat(supplier.get()).asString().isEqualTo("advised: lambda");
+ assertThat(supplier.getClass().getInterfaces())
+ .containsExactlyInAnyOrder(Supplier.class, SpringProxy.class, Advised.class, DecoratingProxy.class);
+ assertThat(supplier.get()).isEqualTo("advised: lambda");
+ }
+ }
+
+ @ParameterizedTest(name = "[{index}] {0}")
+ @ValueSource(classes = {MixinProxyTargetClassFalseConfig.class, MixinProxyTargetClassTrueConfig.class})
+ void lambdaIsAlwaysProxiedWithJdkProxyWithIntroductions(Class> configClass) {
+ try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(configClass)) {
+ MessageGenerator messageGenerator = context.getBean(MessageGenerator.class);
+ assertThat(AopUtils.isAopProxy(messageGenerator)).as("AOP proxy").isTrue();
+ assertThat(AopUtils.isJdkDynamicProxy(messageGenerator)).as("JDK Dynamic proxy").isTrue();
+ assertThat(messageGenerator.getClass().getInterfaces())
+ .containsExactlyInAnyOrder(MessageGenerator.class, Mixin.class, SpringProxy.class, Advised.class, DecoratingProxy.class);
+ assertThat(messageGenerator.generateMessage()).isEqualTo("mixin: lambda");
}
}
@@ -616,3 +641,79 @@ class ProxyTargetClassFalseConfig extends AbstractProxyTargetClassConfig {
@EnableAspectJAutoProxy(proxyTargetClass = true)
class ProxyTargetClassTrueConfig extends AbstractProxyTargetClassConfig {
}
+
+@FunctionalInterface
+interface MessageGenerator {
+ String generateMessage();
+}
+
+interface Mixin {
+}
+
+class MixinIntroductionInterceptor implements IntroductionInterceptor {
+
+ @Override
+ public Object invoke(MethodInvocation invocation) throws Throwable {
+ return "mixin: " + invocation.proceed();
+ }
+
+ @Override
+ public boolean implementsInterface(Class> intf) {
+ return Mixin.class.isAssignableFrom(intf);
+ }
+
+}
+
+@SuppressWarnings("serial")
+class MixinAdvisor extends AbstractPointcutAdvisor implements IntroductionAdvisor {
+
+ @Override
+ public org.springframework.aop.Pointcut getPointcut() {
+ return org.springframework.aop.Pointcut.TRUE;
+ }
+
+ @Override
+ public Advice getAdvice() {
+ return new MixinIntroductionInterceptor();
+ }
+
+ @Override
+ public Class>[] getInterfaces() {
+ return new Class[] { Mixin.class };
+ }
+
+ @Override
+ public ClassFilter getClassFilter() {
+ return MessageGenerator.class::isAssignableFrom;
+ }
+
+ @Override
+ public void validateInterfaces() {
+ /* no-op */
+ }
+
+}
+
+abstract class AbstractMixinConfig {
+
+ @Bean
+ MessageGenerator messageGenerator() {
+ return () -> "lambda";
+ }
+
+ @Bean
+ MixinAdvisor mixinAdvisor() {
+ return new MixinAdvisor();
+ }
+
+}
+
+@Configuration(proxyBeanMethods = false)
+@EnableAspectJAutoProxy(proxyTargetClass = false)
+class MixinProxyTargetClassFalseConfig extends AbstractMixinConfig {
+}
+
+@Configuration(proxyBeanMethods = false)
+@EnableAspectJAutoProxy(proxyTargetClass = true)
+class MixinProxyTargetClassTrueConfig extends AbstractMixinConfig {
+}
diff --git a/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java b/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java
index 3d39e2ecd309..546c599c01f7 100644
--- a/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java
+++ b/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java
@@ -64,23 +64,26 @@
import org.springframework.format.support.FormattingConversionService;
import org.springframework.lang.Nullable;
import org.springframework.tests.sample.beans.BeanWithObjectProperty;
-import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.assertj.core.api.Assertions.entry;
/**
+ * Unit tests for {@link DataBinder}.
+ *
* @author Rod Johnson
* @author Juergen Hoeller
* @author Rob Harrop
* @author Kazuki Shimizu
+ * @author Sam Brannen
*/
class DataBinderTests {
@Test
- void testBindingNoErrors() throws BindException {
+ void bindingNoErrors() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
assertThat(binder.isIgnoreUnknownFields()).isTrue();
@@ -110,12 +113,11 @@ void testBindingNoErrors() throws BindException {
assertThat(ex).isEqualTo(binder.getBindingResult());
other.reject("xxx");
- boolean condition = !other.equals(binder.getBindingResult());
- assertThat(condition).isTrue();
+ assertThat(other).isNotEqualTo(binder.getBindingResult());
}
@Test
- void testBindingWithDefaultConversionNoErrors() throws BindException {
+ void bindingWithDefaultConversionNoErrors() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
assertThat(binder.isIgnoreUnknownFields()).isTrue();
@@ -131,7 +133,7 @@ void testBindingWithDefaultConversionNoErrors() throws BindException {
}
@Test
- void testNestedBindingWithDefaultConversionNoErrors() throws BindException {
+ void nestedBindingWithDefaultConversionNoErrors() throws BindException {
TestBean rod = new TestBean(new TestBean());
DataBinder binder = new DataBinder(rod, "person");
assertThat(binder.isIgnoreUnknownFields()).isTrue();
@@ -147,7 +149,7 @@ void testNestedBindingWithDefaultConversionNoErrors() throws BindException {
}
@Test
- void testBindingNoErrorsNotIgnoreUnknown() {
+ void bindingNoErrorsNotIgnoreUnknown() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
binder.setIgnoreUnknownFields(false);
@@ -160,7 +162,7 @@ void testBindingNoErrorsNotIgnoreUnknown() {
}
@Test
- void testBindingNoErrorsWithInvalidField() {
+ void bindingNoErrorsWithInvalidField() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -171,7 +173,7 @@ void testBindingNoErrorsWithInvalidField() {
}
@Test
- void testBindingNoErrorsWithIgnoreInvalid() {
+ void bindingNoErrorsWithIgnoreInvalid() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
binder.setIgnoreInvalidFields(true);
@@ -180,10 +182,14 @@ void testBindingNoErrorsWithIgnoreInvalid() {
pvs.add("spouse.age", 32);
binder.bind(pvs);
+ binder.close();
+
+ assertThat(rod.getName()).isEqualTo("Rod");
+ assertThat(rod.getSpouse()).isNull();
}
@Test
- void testBindingWithErrors() {
+ void bindingWithErrors() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -245,7 +251,7 @@ void testBindingWithErrors() {
}
@Test
- void testBindingWithSystemFieldError() {
+ void bindingWithSystemFieldError() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -257,7 +263,7 @@ void testBindingWithSystemFieldError() {
}
@Test
- void testBindingWithErrorsAndCustomEditors() {
+ void bindingWithErrorsAndCustomEditors() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
binder.registerCustomEditor(String.class, "touchy", new PropertyEditorSupport() {
@@ -325,7 +331,7 @@ public String getAsText() {
}
@Test
- void testBindingWithCustomEditorOnObjectField() {
+ void bindingWithCustomEditorOnObjectField() {
BeanWithObjectProperty tb = new BeanWithObjectProperty();
DataBinder binder = new DataBinder(tb);
binder.registerCustomEditor(Integer.class, "object", new CustomNumberEditor(Integer.class, true));
@@ -336,7 +342,7 @@ void testBindingWithCustomEditorOnObjectField() {
}
@Test
- void testBindingWithFormatter() {
+ void bindingWithFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -368,7 +374,7 @@ void testBindingWithFormatter() {
}
@Test
- void testBindingErrorWithFormatter() {
+ void bindingErrorWithFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -391,7 +397,7 @@ void testBindingErrorWithFormatter() {
}
@Test
- void testBindingErrorWithParseExceptionFromFormatter() {
+ void bindingErrorWithParseExceptionFromFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -419,7 +425,7 @@ public String print(String object, Locale locale) {
}
@Test
- void testBindingErrorWithRuntimeExceptionFromFormatter() {
+ void bindingErrorWithRuntimeExceptionFromFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -447,7 +453,7 @@ public String print(String object, Locale locale) {
}
@Test
- void testBindingWithFormatterAgainstList() {
+ void bindingWithFormatterAgainstList() {
BeanWithIntegerList tb = new BeanWithIntegerList();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -469,7 +475,7 @@ void testBindingWithFormatterAgainstList() {
}
@Test
- void testBindingErrorWithFormatterAgainstList() {
+ void bindingErrorWithFormatterAgainstList() {
BeanWithIntegerList tb = new BeanWithIntegerList();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -492,7 +498,7 @@ void testBindingErrorWithFormatterAgainstList() {
}
@Test
- void testBindingWithFormatterAgainstFields() {
+ void bindingWithFormatterAgainstFields() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -525,7 +531,7 @@ void testBindingWithFormatterAgainstFields() {
}
@Test
- void testBindingErrorWithFormatterAgainstFields() {
+ void bindingErrorWithFormatterAgainstFields() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
binder.initDirectFieldAccess();
@@ -549,7 +555,7 @@ void testBindingErrorWithFormatterAgainstFields() {
}
@Test
- void testBindingWithCustomFormatter() {
+ void bindingWithCustomFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
binder.addCustomFormatter(new NumberStyleFormatter(), Float.class);
@@ -578,7 +584,7 @@ void testBindingWithCustomFormatter() {
}
@Test
- void testBindingErrorWithCustomFormatter() {
+ void bindingErrorWithCustomFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
binder.addCustomFormatter(new NumberStyleFormatter());
@@ -599,7 +605,7 @@ void testBindingErrorWithCustomFormatter() {
}
@Test
- void testBindingErrorWithParseExceptionFromCustomFormatter() {
+ void bindingErrorWithParseExceptionFromCustomFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
@@ -624,7 +630,7 @@ public String print(String object, Locale locale) {
}
@Test
- void testBindingErrorWithRuntimeExceptionFromCustomFormatter() {
+ void bindingErrorWithRuntimeExceptionFromCustomFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
@@ -649,7 +655,7 @@ public String print(String object, Locale locale) {
}
@Test
- void testConversionWithInappropriateStringEditor() {
+ void conversionWithInappropriateStringEditor() {
DataBinder dataBinder = new DataBinder(null);
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
dataBinder.setConversionService(conversionService);
@@ -662,7 +668,7 @@ void testConversionWithInappropriateStringEditor() {
}
@Test
- void testBindingWithAllowedFields() throws BindException {
+ void bindingWithAllowedFields() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod);
binder.setAllowedFields("name", "myparam");
@@ -672,30 +678,32 @@ void testBindingWithAllowedFields() throws BindException {
binder.bind(pvs);
binder.close();
- assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
- assertThat(rod.getAge() == 0).as("did not change age").isTrue();
+
+ assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
+ assertThat(rod.getAge()).as("did not change age").isZero();
}
@Test
- void testBindingWithDisallowedFields() throws BindException {
+ void bindingWithDisallowedFields() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod);
- binder.setDisallowedFields("age");
+ binder.setDisallowedFields(" ", "\t", "favouriteColour", null, "age");
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("name", "Rod");
pvs.add("age", "32x");
+ pvs.add("favouriteColour", "BLUE");
binder.bind(pvs);
binder.close();
- assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
- assertThat(rod.getAge() == 0).as("did not change age").isTrue();
- String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
- assertThat(disallowedFields.length).isEqualTo(1);
- assertThat(disallowedFields[0]).isEqualTo("age");
+
+ assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
+ assertThat(rod.getAge()).as("did not change age").isZero();
+ assertThat(rod.getFavouriteColour()).as("did not change favourite colour").isNull();
+ assertThat(binder.getBindingResult().getSuppressedFields()).containsExactlyInAnyOrder("age", "favouriteColour");
}
@Test
- void testBindingWithAllowedAndDisallowedFields() throws BindException {
+ void bindingWithAllowedAndDisallowedFields() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod);
binder.setAllowedFields("name", "myparam");
@@ -706,34 +714,32 @@ void testBindingWithAllowedAndDisallowedFields() throws BindException {
binder.bind(pvs);
binder.close();
- assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
- assertThat(rod.getAge() == 0).as("did not change age").isTrue();
- String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
- assertThat(disallowedFields).hasSize(1);
- assertThat(disallowedFields[0]).isEqualTo("age");
+
+ assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
+ assertThat(rod.getAge()).as("did not change age").isZero();
+ assertThat(binder.getBindingResult().getSuppressedFields()).containsExactly("age");
}
@Test
- void testBindingWithOverlappingAllowedAndDisallowedFields() throws BindException {
+ void bindingWithOverlappingAllowedAndDisallowedFields() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod);
binder.setAllowedFields("name", "age");
- binder.setDisallowedFields("age");
+ binder.setDisallowedFields("AGE");
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("name", "Rod");
pvs.add("age", "32x");
binder.bind(pvs);
binder.close();
- assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
- assertThat(rod.getAge() == 0).as("did not change age").isTrue();
- String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
- assertThat(disallowedFields).hasSize(1);
- assertThat(disallowedFields[0]).isEqualTo("age");
+
+ assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
+ assertThat(rod.getAge()).as("did not change age").isZero();
+ assertThat(binder.getBindingResult().getSuppressedFields()).containsExactly("age");
}
@Test
- void testBindingWithAllowedFieldsUsingAsterisks() throws BindException {
+ void bindingWithAllowedFieldsUsingAsterisks() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
binder.setAllowedFields("nam*", "*ouchy");
@@ -760,11 +766,11 @@ void testBindingWithAllowedFieldsUsingAsterisks() throws BindException {
}
@Test
- void testBindingWithAllowedAndDisallowedMapFields() throws BindException {
+ void bindingWithAllowedAndDisallowedMapFields() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod);
binder.setAllowedFields("someMap[key1]", "someMap[key2]");
- binder.setDisallowedFields("someMap['key3']", "someMap[key4]");
+ binder.setDisallowedFields("someMap['KEY3']", "SomeMap[key4]");
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("someMap[key1]", "value1");
@@ -774,21 +780,18 @@ void testBindingWithAllowedAndDisallowedMapFields() throws BindException {
binder.bind(pvs);
binder.close();
- assertThat(rod.getSomeMap().get("key1")).isEqualTo("value1");
- assertThat(rod.getSomeMap().get("key2")).isEqualTo("value2");
- assertThat(rod.getSomeMap().get("key3")).isNull();
- assertThat(rod.getSomeMap().get("key4")).isNull();
- String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
- assertThat(disallowedFields).hasSize(2);
- assertThat(ObjectUtils.containsElement(disallowedFields, "someMap[key3]")).isTrue();
- assertThat(ObjectUtils.containsElement(disallowedFields, "someMap[key4]")).isTrue();
+
+ @SuppressWarnings("unchecked")
+ Map someMap = (Map) rod.getSomeMap();
+ assertThat(someMap).containsOnly(entry("key1", "value1"), entry("key2", "value2"));
+ assertThat(binder.getBindingResult().getSuppressedFields()).containsExactly("someMap[key3]", "someMap[key4]");
}
/**
* Tests for required field, both null, non-existing and empty strings.
*/
@Test
- void testBindingWithRequiredFields() {
+ void bindingWithRequiredFields() {
TestBean tb = new TestBean();
tb.setSpouse(new TestBean());
@@ -819,7 +822,7 @@ void testBindingWithRequiredFields() {
}
@Test
- void testBindingWithRequiredMapFields() {
+ void bindingWithRequiredMapFields() {
TestBean tb = new TestBean();
tb.setSpouse(new TestBean());
@@ -839,7 +842,7 @@ void testBindingWithRequiredMapFields() {
}
@Test
- void testBindingWithNestedObjectCreation() {
+ void bindingWithNestedObjectCreation() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "person");
@@ -860,7 +863,7 @@ public void setAsText(String text) throws IllegalArgumentException {
}
@Test
- void testCustomEditorWithOldValueAccess() {
+ void customEditorWithOldValueAccess() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
@@ -885,7 +888,7 @@ public void setAsText(String text) throws IllegalArgumentException {
}
@Test
- void testCustomEditorForSingleProperty() {
+ void customEditorForSingleProperty() {
TestBean tb = new TestBean();
tb.setSpouse(new TestBean());
DataBinder binder = new DataBinder(tb, "tb");
@@ -925,7 +928,7 @@ public String getAsText() {
}
@Test
- void testCustomEditorForPrimitiveProperty() {
+ void customEditorForPrimitiveProperty() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
@@ -949,7 +952,7 @@ public String getAsText() {
}
@Test
- void testCustomEditorForAllStringProperties() {
+ void customEditorForAllStringProperties() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
@@ -981,7 +984,7 @@ public String getAsText() {
}
@Test
- void testCustomFormatterForSingleProperty() {
+ void customFormatterForSingleProperty() {
TestBean tb = new TestBean();
tb.setSpouse(new TestBean());
DataBinder binder = new DataBinder(tb, "tb");
@@ -1021,7 +1024,7 @@ public String print(String object, Locale locale) {
}
@Test
- void testCustomFormatterForPrimitiveProperty() {
+ void customFormatterForPrimitiveProperty() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
@@ -1045,7 +1048,7 @@ public String print(Integer object, Locale locale) {
}
@Test
- void testCustomFormatterForAllStringProperties() {
+ void customFormatterForAllStringProperties() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
@@ -1077,7 +1080,7 @@ public String print(String object, Locale locale) {
}
@Test
- void testJavaBeanPropertyConventions() {
+ void javaBeanPropertyConventions() {
Book book = new Book();
DataBinder binder = new DataBinder(book);
@@ -1101,7 +1104,7 @@ void testJavaBeanPropertyConventions() {
}
@Test
- void testOptionalProperty() {
+ void optionalProperty() {
OptionalHolder bean = new OptionalHolder();
DataBinder binder = new DataBinder(bean);
binder.setConversionService(new DefaultConversionService());
@@ -1122,7 +1125,7 @@ void testOptionalProperty() {
}
@Test
- void testValidatorNoErrors() throws Exception {
+ void validatorNoErrors() throws Exception {
TestBean tb = new TestBean();
tb.setAge(33);
tb.setName("Rod");
@@ -1175,15 +1178,13 @@ void testValidatorNoErrors() throws Exception {
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
assertThat(errors.getErrorCount()).isEqualTo(1);
- boolean condition1 = !errors.hasGlobalErrors();
- assertThat(condition1).isTrue();
+ assertThat(errors.hasGlobalErrors()).isFalse();
assertThat(errors.getFieldErrorCount("age")).isEqualTo(1);
- boolean condition = !errors.hasFieldErrors("name");
- assertThat(condition).isTrue();
+ assertThat(errors.hasFieldErrors("name")).isFalse();
}
@Test
- void testValidatorWithErrors() {
+ void validatorWithErrors() {
TestBean tb = new TestBean();
tb.setSpouse(new TestBean());
@@ -1252,7 +1253,7 @@ void testValidatorWithErrors() {
}
@Test
- void testValidatorWithErrorsAndCodesPrefix() {
+ void validatorWithErrorsAndCodesPrefix() {
TestBean tb = new TestBean();
tb.setSpouse(new TestBean());
@@ -1324,7 +1325,7 @@ void testValidatorWithErrorsAndCodesPrefix() {
}
@Test
- void testValidatorWithNestedObjectNull() {
+ void validatorWithNestedObjectNull() {
TestBean tb = new TestBean();
Errors errors = new BeanPropertyBindingResult(tb, "tb");
Validator testValidator = new TestBeanValidator();
@@ -1343,7 +1344,7 @@ void testValidatorWithNestedObjectNull() {
}
@Test
- void testNestedValidatorWithoutNestedPath() {
+ void nestedValidatorWithoutNestedPath() {
TestBean tb = new TestBean();
tb.setName("XXX");
Errors errors = new BeanPropertyBindingResult(tb, "tb");
@@ -1357,7 +1358,8 @@ void testNestedValidatorWithoutNestedPath() {
}
@Test
- void testBindingStringArrayToIntegerSet() {
+ @SuppressWarnings("unchecked")
+ void bindingStringArrayToIntegerSet() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(Set.class, new CustomCollectionEditor(TreeSet.class) {
@@ -1371,12 +1373,8 @@ protected Object convertElement(Object element) {
binder.bind(pvs);
assertThat(binder.getBindingResult().getFieldValue("set")).isEqualTo(tb.getSet());
- boolean condition = tb.getSet() instanceof TreeSet;
- assertThat(condition).isTrue();
- assertThat(tb.getSet().size()).isEqualTo(3);
- assertThat(tb.getSet().contains(10)).isTrue();
- assertThat(tb.getSet().contains(20)).isTrue();
- assertThat(tb.getSet().contains(30)).isTrue();
+ assertThat(tb.getSet()).isInstanceOf(TreeSet.class);
+ assertThat((Set) tb.getSet()).containsExactly(10, 20, 30);
pvs = new MutablePropertyValues();
pvs.add("set", null);
@@ -1386,7 +1384,7 @@ protected Object convertElement(Object element) {
}
@Test
- void testBindingNullToEmptyCollection() {
+ void bindingNullToEmptyCollection() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(Set.class, new CustomCollectionEditor(TreeSet.class, true));
@@ -1394,13 +1392,12 @@ void testBindingNullToEmptyCollection() {
pvs.add("set", null);
binder.bind(pvs);
- boolean condition = tb.getSet() instanceof TreeSet;
- assertThat(condition).isTrue();
- assertThat(tb.getSet().isEmpty()).isTrue();
+ assertThat(tb.getSet()).isInstanceOf(TreeSet.class);
+ assertThat(tb.getSet()).isEmpty();
}
@Test
- void testBindingToIndexedField() {
+ void bindingToIndexedField() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(String.class, "array.name", new PropertyEditorSupport() {
@@ -1439,7 +1436,7 @@ public void setAsText(String text) throws IllegalArgumentException {
}
@Test
- void testBindingToNestedIndexedField() {
+ void bindingToNestedIndexedField() {
IndexedTestBean tb = new IndexedTestBean();
tb.getArray()[0].setNestedIndexedBean(new IndexedTestBean());
tb.getArray()[1].setNestedIndexedBean(new IndexedTestBean());
@@ -1470,7 +1467,7 @@ public void setAsText(String text) throws IllegalArgumentException {
}
@Test
- void testEditorForNestedIndexedField() {
+ void editorForNestedIndexedField() {
IndexedTestBean tb = new IndexedTestBean();
tb.getArray()[0].setNestedIndexedBean(new IndexedTestBean());
tb.getArray()[1].setNestedIndexedBean(new IndexedTestBean());
@@ -1496,7 +1493,7 @@ public String getAsText() {
}
@Test
- void testSpecificEditorForNestedIndexedField() {
+ void specificEditorForNestedIndexedField() {
IndexedTestBean tb = new IndexedTestBean();
tb.getArray()[0].setNestedIndexedBean(new IndexedTestBean());
tb.getArray()[1].setNestedIndexedBean(new IndexedTestBean());
@@ -1522,7 +1519,7 @@ public String getAsText() {
}
@Test
- void testInnerSpecificEditorForNestedIndexedField() {
+ void innerSpecificEditorForNestedIndexedField() {
IndexedTestBean tb = new IndexedTestBean();
tb.getArray()[0].setNestedIndexedBean(new IndexedTestBean());
tb.getArray()[1].setNestedIndexedBean(new IndexedTestBean());
@@ -1548,7 +1545,7 @@ public String getAsText() {
}
@Test
- void testDirectBindingToIndexedField() {
+ void directBindingToIndexedField() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(TestBean.class, "array", new PropertyEditorSupport() {
@@ -1601,7 +1598,7 @@ public String getAsText() {
}
@Test
- void testDirectBindingToEmptyIndexedFieldWithRegisteredSpecificEditor() {
+ void directBindingToEmptyIndexedFieldWithRegisteredSpecificEditor() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(TestBean.class, "map[key0]", new PropertyEditorSupport() {
@@ -1632,7 +1629,7 @@ public String getAsText() {
}
@Test
- void testDirectBindingToEmptyIndexedFieldWithRegisteredGenericEditor() {
+ void directBindingToEmptyIndexedFieldWithRegisteredGenericEditor() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(TestBean.class, "map", new PropertyEditorSupport() {
@@ -1663,7 +1660,7 @@ public String getAsText() {
}
@Test
- void testCustomEditorWithSubclass() {
+ void customEditorWithSubclass() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(TestBean.class, new PropertyEditorSupport() {
@@ -1697,7 +1694,7 @@ public String getAsText() {
}
@Test
- void testBindToStringArrayWithArrayEditor() {
+ void bindToStringArrayWithArrayEditor() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(String[].class, "stringArray", new PropertyEditorSupport() {
@@ -1709,15 +1706,12 @@ public void setAsText(String text) throws IllegalArgumentException {
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("stringArray", "a1-b2");
binder.bind(pvs);
- boolean condition = !binder.getBindingResult().hasErrors();
- assertThat(condition).isTrue();
- assertThat(tb.getStringArray().length).isEqualTo(2);
- assertThat(tb.getStringArray()[0]).isEqualTo("a1");
- assertThat(tb.getStringArray()[1]).isEqualTo("b2");
+ assertThat(binder.getBindingResult().hasErrors()).isFalse();
+ assertThat(tb.getStringArray()).containsExactly("a1", "b2");
}
@Test
- void testBindToStringArrayWithComponentEditor() {
+ void bindToStringArrayWithComponentEditor() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(String.class, "stringArray", new PropertyEditorSupport() {
@@ -1729,15 +1723,14 @@ public void setAsText(String text) throws IllegalArgumentException {
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("stringArray", new String[] {"a1", "b2"});
binder.bind(pvs);
- boolean condition = !binder.getBindingResult().hasErrors();
- assertThat(condition).isTrue();
+ assertThat(binder.getBindingResult().hasErrors()).isFalse();
assertThat(tb.getStringArray().length).isEqualTo(2);
assertThat(tb.getStringArray()[0]).isEqualTo("Xa1");
assertThat(tb.getStringArray()[1]).isEqualTo("Xb2");
}
@Test
- void testBindingErrors() {
+ void bindingErrors() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -1764,7 +1757,7 @@ void testBindingErrors() {
}
@Test
- void testAddAllErrors() {
+ void addAllErrors() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -1784,7 +1777,7 @@ void testAddAllErrors() {
@Test
@SuppressWarnings("unchecked")
- void testBindingWithResortedList() {
+ void bindingWithResortedList() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -1802,7 +1795,7 @@ void testBindingWithResortedList() {
}
@Test
- void testRejectWithoutDefaultMessage() {
+ void rejectWithoutDefaultMessage() {
TestBean tb = new TestBean();
tb.setName("myName");
tb.setAge(99);
@@ -1820,7 +1813,7 @@ void testRejectWithoutDefaultMessage() {
}
@Test
- void testBindExceptionSerializable() throws Exception {
+ void bindExceptionSerializable() throws Exception {
SerializablePerson tb = new SerializablePerson();
tb.setName("myName");
tb.setAge(99);
@@ -1849,27 +1842,27 @@ void testBindExceptionSerializable() throws Exception {
}
@Test
- void testTrackDisallowedFields() {
+ void trackDisallowedFields() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.setAllowedFields("name", "age");
String name = "Rob Harrop";
- String beanName = "foobar";
+ int age = 42;
MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("name", name);
- mpvs.add("beanName", beanName);
+ mpvs.add("age", age);
+ mpvs.add("beanName", "foobar");
binder.bind(mpvs);
assertThat(testBean.getName()).isEqualTo(name);
- String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
- assertThat(disallowedFields).hasSize(1);
- assertThat(disallowedFields[0]).isEqualTo("beanName");
+ assertThat(testBean.getAge()).isEqualTo(age);
+ assertThat(binder.getBindingResult().getSuppressedFields()).containsExactly("beanName");
}
@Test
- void testAutoGrowWithinDefaultLimit() {
+ void autoGrowWithinDefaultLimit() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
@@ -1881,7 +1874,7 @@ void testAutoGrowWithinDefaultLimit() {
}
@Test
- void testAutoGrowBeyondDefaultLimit() {
+ void autoGrowBeyondDefaultLimit() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
@@ -1894,7 +1887,7 @@ void testAutoGrowBeyondDefaultLimit() {
}
@Test
- void testAutoGrowWithinCustomLimit() {
+ void autoGrowWithinCustomLimit() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.setAutoGrowCollectionLimit(10);
@@ -1907,7 +1900,7 @@ void testAutoGrowWithinCustomLimit() {
}
@Test
- void testAutoGrowBeyondCustomLimit() {
+ void autoGrowBeyondCustomLimit() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.setAutoGrowCollectionLimit(10);
@@ -1921,7 +1914,7 @@ void testAutoGrowBeyondCustomLimit() {
}
@Test
- void testNestedGrowingList() {
+ void nestedGrowingList() {
Form form = new Form();
DataBinder binder = new DataBinder(form, "form");
MutablePropertyValues mpv = new MutablePropertyValues();
@@ -1937,7 +1930,7 @@ void testNestedGrowingList() {
}
@Test
- void testFieldErrorAccessVariations() {
+ void fieldErrorAccessVariations() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
assertThat(binder.getBindingResult().getGlobalError()).isNull();
@@ -1958,7 +1951,7 @@ void testFieldErrorAccessVariations() {
}
@Test // SPR-14888
- void testSetAutoGrowCollectionLimit() {
+ void setAutoGrowCollectionLimit() {
BeanWithIntegerList tb = new BeanWithIntegerList();
DataBinder binder = new DataBinder(tb);
binder.setAutoGrowCollectionLimit(257);
@@ -1972,7 +1965,7 @@ void testSetAutoGrowCollectionLimit() {
}
@Test // SPR-14888
- void testSetAutoGrowCollectionLimitAfterInitialization() {
+ void setAutoGrowCollectionLimitAfterInitialization() {
DataBinder binder = new DataBinder(new BeanWithIntegerList());
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
assertThatIllegalStateException().isThrownBy(() ->
@@ -1981,7 +1974,7 @@ void testSetAutoGrowCollectionLimitAfterInitialization() {
}
@Test // SPR-15009
- void testSetCustomMessageCodesResolverBeforeInitializeBindingResultForBeanPropertyAccess() {
+ void setCustomMessageCodesResolverBeforeInitializeBindingResultForBeanPropertyAccess() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
DefaultMessageCodesResolver messageCodesResolver = new DefaultMessageCodesResolver();
@@ -1998,7 +1991,7 @@ void testSetCustomMessageCodesResolverBeforeInitializeBindingResultForBeanProper
}
@Test // SPR-15009
- void testSetCustomMessageCodesResolverBeforeInitializeBindingResultForDirectFieldAccess() {
+ void setCustomMessageCodesResolverBeforeInitializeBindingResultForDirectFieldAccess() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
DefaultMessageCodesResolver messageCodesResolver = new DefaultMessageCodesResolver();
@@ -2013,7 +2006,7 @@ void testSetCustomMessageCodesResolverBeforeInitializeBindingResultForDirectFiel
}
@Test // SPR-15009
- void testSetCustomMessageCodesResolverAfterInitializeBindingResult() {
+ void setCustomMessageCodesResolverAfterInitializeBindingResult() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.initBeanPropertyAccess();
@@ -2028,7 +2021,7 @@ void testSetCustomMessageCodesResolverAfterInitializeBindingResult() {
}
@Test // SPR-15009
- void testSetMessageCodesResolverIsNullAfterInitializeBindingResult() {
+ void setMessageCodesResolverIsNullAfterInitializeBindingResult() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.initBeanPropertyAccess();
@@ -2042,8 +2035,7 @@ void testSetMessageCodesResolverIsNullAfterInitializeBindingResult() {
}
@Test // SPR-15009
- void testCallSetMessageCodesResolverTwice() {
-
+ void callSetMessageCodesResolverTwice() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.setMessageCodesResolver(new DefaultMessageCodesResolver());
diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java
index ca7c256cd890..4b81bfe12ac9 100644
--- a/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java
+++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java
@@ -31,6 +31,7 @@
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
+import javax.validation.ConstraintValidatorFactory;
import javax.validation.ConstraintViolation;
import javax.validation.Payload;
import javax.validation.Valid;
@@ -43,6 +44,7 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.convert.support.DefaultConversionService;
@@ -313,6 +315,32 @@ void listValidation() {
validator.destroy();
}
+ @Test
+ void withConstraintValidatorFactory() {
+ ConstraintValidatorFactory cvf = new SpringConstraintValidatorFactory(new DefaultListableBeanFactory());
+
+ @SuppressWarnings("resource")
+ LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
+ validator.setConstraintValidatorFactory(cvf);
+ validator.afterPropertiesSet();
+
+ assertThat(validator.getConstraintValidatorFactory()).isSameAs(cvf);
+ validator.destroy();
+ }
+
+ @Test
+ void withCustomInitializer() {
+ ConstraintValidatorFactory cvf = new SpringConstraintValidatorFactory(new DefaultListableBeanFactory());
+
+ @SuppressWarnings("resource")
+ LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
+ validator.setConfigurationInitializer(configuration -> configuration.constraintValidatorFactory(cvf));
+ validator.afterPropertiesSet();
+
+ assertThat(validator.getConstraintValidatorFactory()).isSameAs(cvf);
+ validator.destroy();
+ }
+
@NameAddressValid
public static class ValidPerson {
@@ -409,8 +437,8 @@ public boolean isValid(ValidPerson value, ConstraintValidatorContext context) {
}
boolean valid = (value.name == null || !value.address.street.contains(value.name));
if (!valid && "Phil".equals(value.name)) {
- context.buildConstraintViolationWithTemplate(
- context.getDefaultConstraintMessageTemplate()).addPropertyNode("address").addConstraintViolation().disableDefaultConstraintViolation();
+ context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
+ .addPropertyNode("address").addConstraintViolation().disableDefaultConstraintViolation();
}
return valid;
}
@@ -446,6 +474,7 @@ public static class InnerBean {
public String getValue() {
return value;
}
+
public void setValue(String value) {
this.value = value;
}
@@ -454,7 +483,7 @@ public void setValue(String value) {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
- @Constraint(validatedBy=InnerValidator.class)
+ @Constraint(validatedBy = InnerValidator.class)
public @interface InnerValid {
String message() default "NOT VALID";
@@ -475,7 +504,8 @@ public void initialize(InnerValid constraintAnnotation) {
public boolean isValid(InnerBean bean, ConstraintValidatorContext context) {
context.disableDefaultConstraintViolation();
if (bean.getValue() == null) {
- context.buildConstraintViolationWithTemplate("NULL").addPropertyNode("value").addConstraintViolation();
+ context.buildConstraintViolationWithTemplate("NULL")
+ .addPropertyNode("value").addConstraintViolation();
return false;
}
return true;
@@ -523,7 +553,8 @@ public boolean isValid(List list, ConstraintValidatorContext context) {
boolean valid = true;
for (int i = 0; i < list.size(); i++) {
if ("X".equals(list.get(i))) {
- context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()).addBeanNode().inIterable().atIndex(i).addConstraintViolation();
+ context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
+ .addBeanNode().inIterable().atIndex(i).addConstraintViolation();
valid = false;
}
}
diff --git a/spring-context/src/test/resources/org/springframework/jmx/export/notificationPublisherTests.xml b/spring-context/src/test/resources/org/springframework/jmx/export/notificationPublisherTests.xml
index 8b8699a8e289..5f4b476586b6 100644
--- a/spring-context/src/test/resources/org/springframework/jmx/export/notificationPublisherTests.xml
+++ b/spring-context/src/test/resources/org/springframework/jmx/export/notificationPublisherTests.xml
@@ -5,19 +5,19 @@
-
+
-
+
-
+
\ No newline at end of file
diff --git a/spring-core/src/main/java/org/springframework/core/convert/Property.java b/spring-core/src/main/java/org/springframework/core/convert/Property.java
index 5cddaea8769f..bf9ae585181a 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/Property.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/Property.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,7 +47,7 @@
*/
public final class Property {
- private static Map annotationCache = new ConcurrentReferenceHashMap<>();
+ private static final Map annotationCache = new ConcurrentReferenceHashMap<>();
private final Class> objectType;
diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java
index ad0419ee4b77..3e9668875661 100644
--- a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java
+++ b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1061,7 +1061,7 @@ protected void hookOnComplete() {
@Override
public Context currentContext() {
- return this.sink.currentContext();
+ return Context.of(this.sink.contextView());
}
}
@@ -1158,7 +1158,7 @@ private void sinkDataBuffer() {
@Override
public Context currentContext() {
- return this.sink.currentContext();
+ return Context.of(this.sink.contextView());
}
}
diff --git a/spring-core/src/main/java/org/springframework/util/ClassUtils.java b/spring-core/src/main/java/org/springframework/util/ClassUtils.java
index 0df6e0ece4c1..d5858c7399c7 100644
--- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java
@@ -842,6 +842,20 @@ public static boolean isInnerClass(Class> clazz) {
return (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers()));
}
+ /**
+ * Determine if the supplied {@link Class} is a JVM-generated implementation
+ * class for a lambda expression or method reference.
+ * This method makes a best-effort attempt at determining this, based on
+ * checks that work on modern, mainstream JVMs.
+ * @param clazz the class to check
+ * @return {@code true} if the class is a lambda implementation class
+ * @since 5.3.19
+ */
+ public static boolean isLambdaClass(Class> clazz) {
+ return (clazz.isSynthetic() && (clazz.getSuperclass() == Object.class) &&
+ (clazz.getInterfaces().length > 0) && clazz.getName().contains("$$Lambda"));
+ }
+
/**
* Check whether the given object is a CGLIB proxy.
* @param object the object to check
diff --git a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java
index 05809bc5ad7b..c7c5468b5896 100644
--- a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java
@@ -65,6 +65,17 @@ public abstract class MimeTypeUtils {
*/
public static final String ALL_VALUE = "*/*";
+ /**
+ * Public constant mime type for {@code application/graphql+json}.
+ * @see GraphQL over HTTP spec
+ * */
+ public static final MimeType APPLICATION_GRAPHQL;
+
+ /**
+ * A String equivalent of {@link MimeTypeUtils#APPLICATION_GRAPHQL}.
+ */
+ public static final String APPLICATION_GRAPHQL_VALUE = "application/graphql+json";
+
/**
* Public constant mime type for {@code application/json}.
* */
@@ -165,6 +176,7 @@ public abstract class MimeTypeUtils {
static {
// Not using "parseMimeType" to avoid static init cost
ALL = new MimeType("*", "*");
+ APPLICATION_GRAPHQL = new MimeType("application", "graphql+json");
APPLICATION_JSON = new MimeType("application", "json");
APPLICATION_OCTET_STREAM = new MimeType("application", "octet-stream");
APPLICATION_XML = new MimeType("application", "xml");
diff --git a/spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java b/spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java
index f14412ba4b18..640ec87eb5b4 100644
--- a/spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java
+++ b/spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Set;
+import java.util.function.Supplier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
@@ -408,6 +409,29 @@ void isPrimitiveOrWrapperWithWrapper(Class> type) {
assertThat(ClassUtils.isPrimitiveOrWrapper(type)).isTrue();
}
+ @Test
+ void isLambda() {
+ assertIsLambda(ClassUtilsTests.staticLambdaExpression);
+ assertIsLambda(ClassUtilsTests::staticStringFactory);
+
+ assertIsLambda(this.instanceLambdaExpression);
+ assertIsLambda(this::instanceStringFactory);
+ }
+
+ @Test
+ void isNotLambda() {
+ assertIsNotLambda(new EnigmaSupplier());
+
+ assertIsNotLambda(new Supplier() {
+ @Override
+ public String get() {
+ return "anonymous inner class";
+ }
+ });
+
+ assertIsNotLambda(new Fake$$LambdaSupplier());
+ }
+
@Nested
class GetStaticMethodTests {
@@ -500,4 +524,38 @@ void print(String header, String[] messages, String footer) {
}
}
+ private static void assertIsLambda(Supplier supplier) {
+ assertThat(ClassUtils.isLambdaClass(supplier.getClass())).isTrue();
+ }
+
+ private static void assertIsNotLambda(Supplier supplier) {
+ assertThat(ClassUtils.isLambdaClass(supplier.getClass())).isFalse();
+ }
+
+ private static final Supplier staticLambdaExpression = () -> "static lambda expression";
+
+ private final Supplier instanceLambdaExpression = () -> "instance lambda expressions";
+
+ private static String staticStringFactory() {
+ return "static string factory";
+ }
+
+ private String instanceStringFactory() {
+ return "instance string factory";
+ }
+
+ private static class EnigmaSupplier implements Supplier {
+ @Override
+ public String get() {
+ return "enigma";
+ }
+ }
+
+ private static class Fake$$LambdaSupplier implements Supplier {
+ @Override
+ public String get() {
+ return "fake lambda";
+ }
+ }
+
}
diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java
index 6f4f3c8c69cc..c425c84746ea 100644
--- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java
@@ -288,8 +288,8 @@ private TypedValue createArray(ExpressionState state) throws EvaluationException
else {
// There is an initializer
if (this.dimensions == null || this.dimensions.length > 1) {
- // There is an initializer but this is a multi-dimensional array (e.g. new int[][]{{1,2},{3,4}}) - this
- // is not currently supported
+ // There is an initializer but this is a multi-dimensional array (e.g. new int[][]{{1,2},{3,4}})
+ // - this is not currently supported
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.MULTIDIM_ARRAY_INITIALIZER_NOT_SUPPORTED);
}
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/DataClassRowMapper.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/DataClassRowMapper.java
index 7e09fcbe8919..274bfdfa0cbf 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/DataClassRowMapper.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/DataClassRowMapper.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -98,9 +98,18 @@ protected T constructMappedInstance(ResultSet rs, TypeConverter tc) throws SQLEx
if (this.constructorParameterNames != null && this.constructorParameterTypes != null) {
args = new Object[this.constructorParameterNames.length];
for (int i = 0; i < args.length; i++) {
- String name = underscoreName(this.constructorParameterNames[i]);
+ String name = this.constructorParameterNames[i];
+ int index;
+ try {
+ // Try direct name match first
+ index = rs.findColumn(lowerCaseName(name));
+ }
+ catch (SQLException ex) {
+ // Try underscored name match instead
+ index = rs.findColumn(underscoreName(name));
+ }
TypeDescriptor td = this.constructorParameterTypes[i];
- Object value = getColumnValue(rs, rs.findColumn(name), td.getType());
+ Object value = getColumnValue(rs, index, td.getType());
args[i] = tc.convertIfNecessary(value, td.getType(), td);
}
}
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java
index 1c0a86ffed97..cf9d5817b0c0 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java
@@ -20,6 +20,7 @@
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Statement;
import java.sql.Timestamp;
@@ -63,7 +64,7 @@ protected void verifyPerson(Person person) {
protected void verifyPerson(ConcretePerson person) {
assertThat(person.getName()).isEqualTo("Bubba");
assertThat(person.getAge()).isEqualTo(22L);
- assertThat(person.getBirth_date()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
+ assertThat(person.getBirthDate()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
assertThat(person.getBalance()).isEqualTo(new BigDecimal("1234.56"));
verifyPersonViaBeanWrapper(person);
}
@@ -94,7 +95,14 @@ private void verifyPersonViaBeanWrapper(Object person) {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(person);
assertThat(bw.getPropertyValue("name")).isEqualTo("Bubba");
assertThat(bw.getPropertyValue("age")).isEqualTo(22L);
- assertThat((Date) bw.getPropertyValue("birth_date")).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
+ Date birthDate;
+ if (bw.isReadableProperty("birth_date")) {
+ birthDate = (Date) bw.getPropertyValue("birth_date");
+ }
+ else {
+ birthDate = (Date) bw.getPropertyValue("birthDate");
+ }
+ assertThat(birthDate).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
assertThat(bw.getPropertyValue("balance")).isEqualTo(new BigDecimal("1234.56"));
}
@@ -107,7 +115,7 @@ protected void verifyPerson(EmailPerson person) {
}
- protected enum MockType {ONE, TWO, THREE}
+ protected enum MockType {ONE, TWO, THREE, FOUR}
protected static class Mock {
@@ -152,13 +160,19 @@ public Mock(MockType type) throws Exception {
given(resultSetMetaData.getColumnLabel(1)).willReturn(
type == MockType.THREE ? "Last Name" : "name");
given(resultSetMetaData.getColumnLabel(2)).willReturn("age");
- given(resultSetMetaData.getColumnLabel(3)).willReturn("birth_date");
+ given(resultSetMetaData.getColumnLabel(3)).willReturn(type == MockType.FOUR ? "birthdate" :"birth_date");
given(resultSetMetaData.getColumnLabel(4)).willReturn("balance");
given(resultSetMetaData.getColumnLabel(5)).willReturn("e_mail");
given(resultSet.findColumn("name")).willReturn(1);
given(resultSet.findColumn("age")).willReturn(2);
- given(resultSet.findColumn("birth_date")).willReturn(3);
+ if (type == MockType.FOUR) {
+ given(resultSet.findColumn("birthdate")).willReturn(3);
+ }
+ else {
+ given(resultSet.findColumn("birthdate")).willThrow(new SQLException());
+ given(resultSet.findColumn("birth_date")).willReturn(3);
+ }
given(resultSet.findColumn("balance")).willReturn(4);
given(resultSet.findColumn("e_mail")).willReturn(5);
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java
index 99e9eb416274..5ef1f57f8916 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java
@@ -140,6 +140,17 @@ void queryWithSpaceInColumnNameAndLocalDate() throws Exception {
mock.verifyClosed();
}
+ @Test
+ void queryWithDirectNameMatchOnBirthDate() throws Exception {
+ Mock mock = new Mock(MockType.FOUR);
+ List result = mock.getJdbcTemplate().query(
+ "select name, age, birthdate, balance from people",
+ new BeanPropertyRowMapper<>(ConcretePerson.class));
+ assertThat(result).hasSize(1);
+ verifyPerson(result.get(0));
+ mock.verifyClosed();
+ }
+
@Test
void queryWithUnderscoreInColumnNameAndPersonWithMultipleAdjacentUppercaseLettersInPropertyName() throws Exception {
Mock mock = new Mock();
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/DataClassRowMapperTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/DataClassRowMapperTests.java
index 48b0f7f03134..c612e5bcae63 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/DataClassRowMapperTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/DataClassRowMapperTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -57,7 +57,7 @@ public void testStaticQueryWithDataClassAndGenerics() throws Exception {
ConstructorPersonWithGenerics person = result.get(0);
assertThat(person.name()).isEqualTo("Bubba");
assertThat(person.age()).isEqualTo(22L);
- assertThat(person.birth_date()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
+ assertThat(person.birthDate()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
assertThat(person.balance()).isEqualTo(Collections.singletonList(new BigDecimal("1234.56")));
mock.verifyClosed();
@@ -65,15 +65,15 @@ public void testStaticQueryWithDataClassAndGenerics() throws Exception {
@Test
public void testStaticQueryWithDataClassAndSetters() throws Exception {
- Mock mock = new Mock();
+ Mock mock = new Mock(MockType.FOUR);
List result = mock.getJdbcTemplate().query(
- "select name, age, birth_date, balance from people",
+ "select name, age, birthdate, balance from people",
new DataClassRowMapper<>(ConstructorPersonWithSetters.class));
assertThat(result.size()).isEqualTo(1);
ConstructorPersonWithSetters person = result.get(0);
assertThat(person.name()).isEqualTo("BUBBA");
assertThat(person.age()).isEqualTo(22L);
- assertThat(person.birth_date()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
+ assertThat(person.birthDate()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
assertThat(person.balance()).isEqualTo(new BigDecimal("1234.56"));
mock.verifyClosed();
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/AbstractPerson.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/AbstractPerson.java
index f2698d3073ac..b084644c6896 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/AbstractPerson.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/AbstractPerson.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@ public abstract class AbstractPerson {
private long age;
- private Date birth_date;
+ private Date birthDate;
public String getName() {
@@ -46,12 +46,12 @@ public void setAge(long age) {
this.age = age;
}
- public Date getBirth_date() {
- return birth_date;
+ public Date getBirthDate() {
+ return birthDate;
}
- public void setBirth_date(Date birth_date) {
- this.birth_date = birth_date;
+ public void setBirthDate(Date birthDate) {
+ this.birthDate = birthDate;
}
}
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/ConstructorPersonWithGenerics.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/ConstructorPersonWithGenerics.java
index 3ae8e271c810..289197b56392 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/ConstructorPersonWithGenerics.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/ConstructorPersonWithGenerics.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ public class ConstructorPersonWithGenerics {
private final long age;
- private final Date birth_date;
+ private final Date birthDate;
private final List balance;
@@ -37,7 +37,7 @@ public class ConstructorPersonWithGenerics {
public ConstructorPersonWithGenerics(String name, long age, Date birth_date, List balance) {
this.name = name;
this.age = age;
- this.birth_date = birth_date;
+ this.birthDate = birth_date;
this.balance = balance;
}
@@ -50,8 +50,8 @@ public long age() {
return this.age;
}
- public Date birth_date() {
- return this.birth_date;
+ public Date birthDate() {
+ return this.birthDate;
}
public List balance() {
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/ConstructorPersonWithSetters.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/ConstructorPersonWithSetters.java
index ef1feb9a324d..0776b5cc48ab 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/ConstructorPersonWithSetters.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/ConstructorPersonWithSetters.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,15 +28,15 @@ public class ConstructorPersonWithSetters {
private long age;
- private Date birth_date;
+ private Date birthDate;
private BigDecimal balance;
- public ConstructorPersonWithSetters(String name, long age, Date birth_date, BigDecimal balance) {
+ public ConstructorPersonWithSetters(String name, long age, Date birthDate, BigDecimal balance) {
this.name = name.toUpperCase();
this.age = age;
- this.birth_date = birth_date;
+ this.birthDate = birthDate;
this.balance = balance;
}
@@ -49,8 +49,8 @@ public void setAge(long age) {
this.age = age;
}
- public void setBirth_date(Date birth_date) {
- this.birth_date = birth_date;
+ public void setBirthDate(Date birthDate) {
+ this.birthDate = birthDate;
}
public void setBalance(BigDecimal balance) {
@@ -65,8 +65,8 @@ public long age() {
return this.age;
}
- public Date birth_date() {
- return this.birth_date;
+ public Date birthDate() {
+ return this.birthDate;
}
public BigDecimal balance() {
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/SpacePerson.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/SpacePerson.java
index 8dc8875e15c8..2fc59db1b2e6 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/SpacePerson.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/SpacePerson.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2016 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -60,8 +60,8 @@ public BigDecimal getBalance() {
return balance;
}
- public void setBalance(BigDecimal balanace) {
- this.balance = balanace;
+ public void setBalance(BigDecimal balance) {
+ this.balance = balance;
}
}
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/HandlerMethod.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/HandlerMethod.java
index 4ace844929fb..7a834c5d2f07 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/handler/HandlerMethod.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/HandlerMethod.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -420,21 +420,21 @@ public HandlerMethodParameter clone() {
private class ReturnValueMethodParameter extends HandlerMethodParameter {
@Nullable
- private final Object returnValue;
+ private final Class> returnValueType;
public ReturnValueMethodParameter(@Nullable Object returnValue) {
super(-1);
- this.returnValue = returnValue;
+ this.returnValueType = (returnValue != null ? returnValue.getClass() : null);
}
protected ReturnValueMethodParameter(ReturnValueMethodParameter original) {
super(original);
- this.returnValue = original.returnValue;
+ this.returnValueType = original.returnValueType;
}
@Override
public Class> getParameterType() {
- return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType());
+ return (this.returnValueType != null ? this.returnValueType : super.getParameterType());
}
@Override
diff --git a/spring-web/src/main/java/org/springframework/http/MediaType.java b/spring-web/src/main/java/org/springframework/http/MediaType.java
index 729555add951..2a784e0386b1 100644
--- a/spring-web/src/main/java/org/springframework/http/MediaType.java
+++ b/spring-web/src/main/java/org/springframework/http/MediaType.java
@@ -95,6 +95,17 @@ public class MediaType extends MimeType implements Serializable {
*/
public static final String APPLICATION_FORM_URLENCODED_VALUE = "application/x-www-form-urlencoded";
+ /**
+ * Public constant media type for {@code application/graphql+json}.
+ * @see GraphQL over HTTP spec
+ */
+ public static final MediaType APPLICATION_GRAPHQL;
+
+ /**
+ * A String equivalent of {@link MediaType#APPLICATION_GRAPHQL}.
+ */
+ public static final String APPLICATION_GRAPHQL_VALUE = "application/graphql+json";
+
/**
* Public constant media type for {@code application/json}.
*/
@@ -396,6 +407,7 @@ public class MediaType extends MimeType implements Serializable {
APPLICATION_ATOM_XML = new MediaType("application", "atom+xml");
APPLICATION_CBOR = new MediaType("application", "cbor");
APPLICATION_FORM_URLENCODED = new MediaType("application", "x-www-form-urlencoded");
+ APPLICATION_GRAPHQL = new MediaType("application", "graphql+json");
APPLICATION_JSON = new MediaType("application", "json");
APPLICATION_JSON_UTF8 = new MediaType("application", "json", StandardCharsets.UTF_8);
APPLICATION_NDJSON = new MediaType("application", "x-ndjson");
diff --git a/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartParser.java b/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartParser.java
index ff1344424aa6..7ef9e9aa9e44 100644
--- a/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartParser.java
+++ b/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -105,7 +105,7 @@ public static Flux parse(Flux buffers, byte[] boundary, int m
@Override
public Context currentContext() {
- return this.sink.currentContext();
+ return Context.of(this.sink.contextView());
}
@Override
diff --git a/spring-web/src/main/java/org/springframework/http/codec/multipart/PartGenerator.java b/spring-web/src/main/java/org/springframework/http/codec/multipart/PartGenerator.java
index 88d689d90e9b..484a09c6adba 100644
--- a/spring-web/src/main/java/org/springframework/http/codec/multipart/PartGenerator.java
+++ b/spring-web/src/main/java/org/springframework/http/codec/multipart/PartGenerator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -116,7 +116,7 @@ public static Flux createParts(Flux tokens, int max
@Override
public Context currentContext() {
- return this.sink.currentContext();
+ return Context.of(this.sink.contextView());
}
@Override
diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java
index 82f722310c3b..45ce146f2ef8 100644
--- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java
+++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java
@@ -197,7 +197,7 @@ public Principal getPrincipal() {
@Override
public InetSocketAddress getLocalAddress() {
- return new InetSocketAddress(this.servletRequest.getLocalName(), this.servletRequest.getLocalPort());
+ return new InetSocketAddress(this.servletRequest.getLocalAddr(), this.servletRequest.getLocalPort());
}
@Override
diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerReadPublisher.java b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerReadPublisher.java
index 0845a9f25f04..de1f3ca5a600 100644
--- a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerReadPublisher.java
+++ b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerReadPublisher.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,8 @@
import org.reactivestreams.Subscription;
import reactor.core.publisher.Operators;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.core.log.LogDelegateFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -56,6 +58,8 @@ public abstract class AbstractListenerReadPublisher implements Publisher {
*/
protected static Log rsReadLogger = LogDelegateFactory.getHiddenLog(AbstractListenerReadPublisher.class);
+ final static DataBuffer EMPTY_BUFFER = DefaultDataBufferFactory.sharedInstance.allocateBuffer(0);
+
private final AtomicReference state = new AtomicReference<>(State.UNSUBSCRIBED);
@@ -180,7 +184,7 @@ public final void onError(Throwable ex) {
/**
* Read and publish data one at a time until there is no more data, no more
- * demand, or perhaps we completed in the mean time.
+ * demand, or perhaps we completed meanwhile.
* @return {@code true} if there is more demand; {@code false} if there is
* no more demand or we have completed.
*/
@@ -188,7 +192,12 @@ private boolean readAndPublish() throws IOException {
long r;
while ((r = this.demand) > 0 && (this.state.get() != State.COMPLETED)) {
T data = read();
- if (data != null) {
+ if (data == EMPTY_BUFFER) {
+ if (rsReadLogger.isTraceEnabled()) {
+ rsReadLogger.trace(getLogPrefix() + "0 bytes read, trying again");
+ }
+ }
+ else if (data != null) {
if (r != Long.MAX_VALUE) {
DEMAND_FIELD_UPDATER.addAndGet(this, -1L);
}
diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpRequest.java
index a84ddc6d6e3d..51fa59839afb 100644
--- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpRequest.java
+++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpRequest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -198,7 +198,7 @@ public InetSocketAddress getRemoteAddress() {
@Nullable
protected SslInfo initSslInfo() {
X509Certificate[] certificates = getX509Certificates();
- return certificates != null ? new DefaultSslInfo(getSslSessionId(), certificates) : null;
+ return (certificates != null ? new DefaultSslInfo(getSslSessionId(), certificates) : null);
}
@Nullable
@@ -208,8 +208,7 @@ private String getSslSessionId() {
@Nullable
private X509Certificate[] getX509Certificates() {
- String name = "javax.servlet.request.X509Certificate";
- return (X509Certificate[]) this.request.getAttribute(name);
+ return (X509Certificate[]) this.request.getAttribute("javax.servlet.request.X509Certificate");
}
@Override
@@ -236,10 +235,10 @@ AsyncListener getAsyncListener() {
/**
* Read from the request body InputStream and return a DataBuffer.
* Invoked only when {@link ServletInputStream#isReady()} returns "true".
- * @return a DataBuffer with data read, or {@link #EOF_BUFFER} if the input
- * stream returned -1, or null if 0 bytes were read.
+ * @return a DataBuffer with data read, or
+ * {@link AbstractListenerReadPublisher#EMPTY_BUFFER} if 0 bytes were read,
+ * or {@link #EOF_BUFFER} if the input stream returned -1.
*/
- @Nullable
DataBuffer readFromInputStream() throws IOException {
int read = this.request.getInputStream().read(this.buffer);
logBytesRead(read);
@@ -254,7 +253,7 @@ DataBuffer readFromInputStream() throws IOException {
return EOF_BUFFER;
}
- return null;
+ return AbstractListenerReadPublisher.EMPTY_BUFFER;
}
protected final void logBytesRead(int read) {
diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java b/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java
index 7920c7ffd8b4..b8c78fdbf4e4 100644
--- a/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java
+++ b/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java
@@ -153,7 +153,7 @@ else if (read == -1) {
return EOF_BUFFER;
}
else {
- return null;
+ return AbstractListenerReadPublisher.EMPTY_BUFFER;
}
}
finally {
diff --git a/spring-web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java b/spring-web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java
index 16bf30ee01fc..864e21843984 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,6 +33,15 @@
* Special {@link org.springframework.validation.DataBinder} to perform data binding
* from servlet request parameters to JavaBeans, including support for multipart files.
*
+ * WARNING: Data binding can lead to security issues by exposing
+ * parts of the object graph that are not meant to be accessed or modified by
+ * external clients. Therefore the design and use of data binding should be considered
+ * carefully with regard to security. For more details, please refer to the dedicated
+ * sections on data binding for
+ * Spring Web MVC and
+ * Spring WebFlux
+ * in the reference manual.
+ *
*
See the DataBinder/WebDataBinder superclasses for customization options,
* which include specifying allowed/required fields, and registering custom
* property editors.
diff --git a/spring-web/src/main/java/org/springframework/web/bind/WebDataBinder.java b/spring-web/src/main/java/org/springframework/web/bind/WebDataBinder.java
index a1cd50ad7443..754f72ac5493 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/WebDataBinder.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/WebDataBinder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,6 +34,15 @@
* the Servlet API; serves as base class for more specific DataBinder variants,
* such as {@link org.springframework.web.bind.ServletRequestDataBinder}.
*
+ *
WARNING: Data binding can lead to security issues by exposing
+ * parts of the object graph that are not meant to be accessed or modified by
+ * external clients. Therefore the design and use of data binding should be considered
+ * carefully with regard to security. For more details, please refer to the dedicated
+ * sections on data binding for
+ * Spring Web MVC and
+ * Spring WebFlux
+ * in the reference manual.
+ *
*
Includes support for field markers which address a common problem with
* HTML checkboxes and select options: detecting that a field was part of
* the form, but did not generate a request parameter because it was empty.
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/ExceptionHandler.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/ExceptionHandler.java
index 3f48fa431185..467d9853ef2e 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/ExceptionHandler.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/ExceptionHandler.java
@@ -100,6 +100,7 @@
* @author Arjen Poutsma
* @author Juergen Hoeller
* @since 3.0
+ * @see ControllerAdvice
* @see org.springframework.web.context.request.WebRequest
*/
@Target(ElementType.METHOD)
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/InitBinder.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/InitBinder.java
index 5fc5d6bcc279..370b2f2801e0 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/InitBinder.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/InitBinder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,15 +23,24 @@
import java.lang.annotation.Target;
/**
- * Annotation that identifies methods which initialize the
+ * Annotation that identifies methods that initialize the
* {@link org.springframework.web.bind.WebDataBinder} which
* will be used for populating command and form object arguments
* of annotated handler methods.
*
- *
Such init-binder methods support all arguments that {@link RequestMapping}
- * supports, except for command/form objects and corresponding validation result
- * objects. Init-binder methods must not have a return value; they are usually
- * declared as {@code void}.
+ *
WARNING: Data binding can lead to security issues by exposing
+ * parts of the object graph that are not meant to be accessed or modified by
+ * external clients. Therefore the design and use of data binding should be considered
+ * carefully with regard to security. For more details, please refer to the dedicated
+ * sections on data binding for
+ * Spring Web MVC and
+ * Spring WebFlux
+ * in the reference manual.
+ *
+ *
{@code @InitBinder} methods support all arguments that
+ * {@link RequestMapping @RequestMapping} methods support, except for command/form
+ * objects and corresponding validation result objects. {@code @InitBinder} methods
+ * must not have a return value; they are usually declared as {@code void}.
*
*
Typical arguments are {@link org.springframework.web.bind.WebDataBinder}
* in combination with {@link org.springframework.web.context.request.WebRequest}
@@ -39,6 +48,7 @@
*
* @author Juergen Hoeller
* @since 2.5
+ * @see ControllerAdvice
* @see org.springframework.web.bind.WebDataBinder
* @see org.springframework.web.context.request.WebRequest
*/
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/ModelAttribute.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/ModelAttribute.java
index 717a6d0106ef..3316065a0760 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/ModelAttribute.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/ModelAttribute.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2016 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,18 +31,27 @@
* for controller classes with {@link RequestMapping @RequestMapping}
* methods.
*
- *
Can be used to expose command objects to a web view, using
- * specific attribute names, through annotating corresponding
- * parameters of an {@link RequestMapping @RequestMapping} method.
+ *
WARNING: Data binding can lead to security issues by exposing
+ * parts of the object graph that are not meant to be accessed or modified by
+ * external clients. Therefore the design and use of data binding should be considered
+ * carefully with regard to security. For more details, please refer to the dedicated
+ * sections on data binding for
+ * Spring Web MVC and
+ * Spring WebFlux
+ * in the reference manual.
*
- *
Can also be used to expose reference data to a web view
- * through annotating accessor methods in a controller class with
+ *
{@code @ModelAttribute} can be used to expose command objects to a web view,
+ * using specific attribute names, by annotating corresponding parameters of an
+ * {@link RequestMapping @RequestMapping} method.
+ *
+ *
{@code @ModelAttribute} can also be used to expose reference data to a web
+ * view by annotating accessor methods in a controller class with
* {@link RequestMapping @RequestMapping} methods. Such accessor
* methods are allowed to have any arguments that
* {@link RequestMapping @RequestMapping} methods support, returning
* the model attribute value to expose.
*
- *
Note however that reference data and all other model content is
+ *
Note however that reference data and all other model content are
* not available to web views when request processing results in an
* {@code Exception} since the exception could be raised at any time
* making the content of the model unreliable. For this reason
@@ -52,6 +61,7 @@
* @author Juergen Hoeller
* @author Rossen Stoyanchev
* @since 2.5
+ * @see ControllerAdvice
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@@ -77,7 +87,7 @@
String name() default "";
/**
- * Allows declaring data binding disabled directly on an {@code @ModelAttribute}
+ * Allows data binding to be disabled directly on an {@code @ModelAttribute}
* method parameter or on the attribute returned from an {@code @ModelAttribute}
* method, both of which would prevent data binding for that attribute.
*
By default this is set to {@code true} in which case data binding applies.
diff --git a/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java b/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java
index ed7855e79097..b4957970a5d2 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,6 +36,15 @@
* Specialized {@link org.springframework.validation.DataBinder} to perform data
* binding from URL query parameters or form data in the request data to Java objects.
*
+ *
WARNING: Data binding can lead to security issues by exposing
+ * parts of the object graph that are not meant to be accessed or modified by
+ * external clients. Therefore the design and use of data binding should be considered
+ * carefully with regard to security. For more details, please refer to the dedicated
+ * sections on data binding for
+ * Spring Web MVC and
+ * Spring WebFlux
+ * in the reference manual.
+ *
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 5.0
diff --git a/spring-web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java b/spring-web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java
index 76ea4abddab4..805667f69b87 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,6 +35,15 @@
* Special {@link org.springframework.validation.DataBinder} to perform data binding
* from web request parameters to JavaBeans, including support for multipart files.
*
+ *
WARNING: Data binding can lead to security issues by exposing
+ * parts of the object graph that are not meant to be accessed or modified by
+ * external clients. Therefore the design and use of data binding should be considered
+ * carefully with regard to security. For more details, please refer to the dedicated
+ * sections on data binding for
+ * Spring Web MVC and
+ * Spring WebFlux
+ * in the reference manual.
+ *
*
See the DataBinder/WebDataBinder superclasses for customization options,
* which include specifying allowed/required fields, and registering custom
* property editors.
diff --git a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java
index 80d6e7999eb4..ca859130f69f 100644
--- a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java
+++ b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -582,21 +582,21 @@ public HandlerMethodParameter clone() {
private class ReturnValueMethodParameter extends HandlerMethodParameter {
@Nullable
- private final Object returnValue;
+ private final Class> returnValueType;
public ReturnValueMethodParameter(@Nullable Object returnValue) {
super(-1);
- this.returnValue = returnValue;
+ this.returnValueType = (returnValue != null ? returnValue.getClass() : null);
}
protected ReturnValueMethodParameter(ReturnValueMethodParameter original) {
super(original);
- this.returnValue = original.returnValue;
+ this.returnValueType = original.returnValueType;
}
@Override
public Class> getParameterType() {
- return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType());
+ return (this.returnValueType != null ? this.returnValueType : super.getParameterType());
}
@Override
diff --git a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonEncoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonEncoderTests.java
index 2e589ac2a8f8..b66084e8299a 100644
--- a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonEncoderTests.java
+++ b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonEncoderTests.java
@@ -103,7 +103,7 @@ public void encode() throws Exception {
);
}
- @Test // SPR-15866
+ @Test // SPR-15866
public void canEncodeWithCustomMimeType() {
MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8);
Jackson2JsonEncoder encoder = new Jackson2JsonEncoder(new ObjectMapper(), textJavascript);
@@ -231,9 +231,8 @@ public void jacksonValue() {
);
}
- @Test // gh-28045
+ @Test // gh-28045
public void jacksonValueUnwrappedBeforeObjectMapperSelection() {
-
JacksonViewBean bean = new JacksonViewBean();
bean.setWithView1("with");
bean.setWithView2("with");
@@ -248,13 +247,15 @@ public void jacksonValueUnwrappedBeforeObjectMapperSelection() {
ObjectMapper mapper = new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true);
this.encoder.registerObjectMappersForType(JacksonViewBean.class, map -> map.put(halMediaType, mapper));
+ String ls = System.lineSeparator(); // output below is different between Unix and Windows
testEncode(Mono.just(jacksonValue), type, halMediaType, Collections.emptyMap(), step -> step
- .consumeNextWith(expectString("{\n \"withView1\" : \"with\"\n}").andThen(DataBufferUtils::release))
+ .consumeNextWith(expectString("{" + ls + " \"withView1\" : \"with\"" + ls + "}")
+ .andThen(DataBufferUtils::release))
.verifyComplete()
);
}
- @Test // gh-22771
+ @Test // gh-22771
public void encodeWithFlushAfterWriteOff() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FLUSH_AFTER_WRITE_VALUE, false);
diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/InitBinderDataBinderFactoryTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/InitBinderDataBinderFactoryTests.java
index 1126d2bf2516..ce284f935e71 100644
--- a/spring-web/src/test/java/org/springframework/web/method/annotation/InitBinderDataBinderFactoryTests.java
+++ b/spring-web/src/test/java/org/springframework/web/method/annotation/InitBinderDataBinderFactoryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -116,7 +116,7 @@ public void createBinderTypeConversion() throws Exception {
WebDataBinder dataBinder = factory.createBinder(this.webRequest, null, "foo");
assertThat(dataBinder.getDisallowedFields()).isNotNull();
- assertThat(dataBinder.getDisallowedFields()[0]).isEqualTo("requestParam-22");
+ assertThat(dataBinder.getDisallowedFields()[0]).isEqualToIgnoringCase("requestParam-22");
}
private WebDataBinderFactory createFactory(String methodName, Class>... parameterTypes)
diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessorTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessorTests.java
index bc3be0e7aa99..c3ab1ed07256 100644
--- a/spring-web/src/test/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessorTests.java
+++ b/spring-web/src/test/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -269,7 +269,7 @@ public void handleNotAnnotatedReturnValue() throws Exception {
assertThat(this.container.getModel().get("testBean")).isSameAs(testBean);
}
- @Test // gh-25182
+ @Test // gh-25182
public void resolveConstructorListArgumentFromCommaSeparatedRequestParameter() throws Exception {
MockHttpServletRequest mockRequest = new MockHttpServletRequest();
mockRequest.addParameter("listOfStrings", "1,2");
@@ -279,7 +279,6 @@ public void resolveConstructorListArgumentFromCommaSeparatedRequestParameter() t
given(factory.createBinder(any(), any(), eq("testBeanWithConstructorArgs")))
.willAnswer(invocation -> {
WebRequestDataBinder binder = new WebRequestDataBinder(invocation.getArgument(1));
-
// Add conversion service which will convert "1,2" to a list
binder.setConversionService(new DefaultFormattingConversionService());
return binder;
@@ -309,7 +308,6 @@ private static class StubRequestDataBinder extends WebRequestDataBinder {
private boolean validateInvoked;
-
public StubRequestDataBinder(Object target, String objectName) {
super(target, objectName);
}
@@ -345,7 +343,7 @@ public void validate(Object... validationHints) {
}
- @SessionAttributes(types=TestBean.class)
+ @SessionAttributes(types = TestBean.class)
private static class ModelAttributeHandler {
@SuppressWarnings("unused")
@@ -360,6 +358,7 @@ public void modelAttribute(
}
}
+
static class TestBeanWithConstructorArgs {
final List listOfStrings;
@@ -367,15 +366,15 @@ static class TestBeanWithConstructorArgs {
public TestBeanWithConstructorArgs(List listOfStrings) {
this.listOfStrings = listOfStrings;
}
-
}
- @ModelAttribute("modelAttrName") @SuppressWarnings("unused")
+
+ @ModelAttribute("modelAttrName")
+ @SuppressWarnings("unused")
private String annotatedReturnValue() {
return null;
}
-
@SuppressWarnings("unused")
private TestBean notAnnotatedReturnValue() {
return null;
diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/InitBinderBindingContextTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/InitBinderBindingContextTests.java
index 56ba84873cca..d695a3f750c6 100644
--- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/InitBinderBindingContextTests.java
+++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/InitBinderBindingContextTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -121,7 +121,7 @@ public void createBinderTypeConversion() throws Exception {
WebDataBinder dataBinder = context.createDataBinder(exchange, null, "foo");
assertThat(dataBinder.getDisallowedFields()).isNotNull();
- assertThat(dataBinder.getDisallowedFields()[0]).isEqualTo("requestParam-22");
+ assertThat(dataBinder.getDisallowedFields()[0]).isEqualToIgnoringCase("requestParam-22");
}
diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java
index 9f83dff6601b..b60587452ac1 100644
--- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java
+++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartIntegrationTests.java
@@ -25,7 +25,6 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
-import org.junit.jupiter.api.Disabled;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
@@ -63,7 +62,6 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
-@Disabled
class MultipartIntegrationTests extends AbstractHttpHandlerIntegrationTests {
private WebClient webClient;
diff --git a/spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/method/annotation/CoroutinesIntegrationTests.kt b/spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/method/annotation/CoroutinesIntegrationTests.kt
index 56e25eb9fc51..18ab6e1dda78 100644
--- a/spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/method/annotation/CoroutinesIntegrationTests.kt
+++ b/spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/method/annotation/CoroutinesIntegrationTests.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +24,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType
-import org.junit.jupiter.api.Disabled
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.ComponentScan
@@ -40,7 +39,6 @@ import org.springframework.web.testfixture.http.server.reactive.bootstrap.HttpSe
import reactor.core.publisher.Flux
import java.time.Duration
-@Disabled
class CoroutinesIntegrationTests : AbstractRequestMappingIntegrationTests() {
override fun initApplicationContext(): ApplicationContext {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java
index 158a33b9c918..8152b9194744 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java
@@ -242,12 +242,12 @@ protected void writeWithMessageConverters(@Nullable T value, MethodParameter
}
}
if (mediaTypesToUse.isEmpty()) {
- if (body != null) {
- throw new HttpMediaTypeNotAcceptableException(producibleTypes);
- }
if (logger.isDebugEnabled()) {
logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
}
+ if (body != null) {
+ throw new HttpMediaTypeNotAcceptableException(producibleTypes);
+ }
return;
}
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 2a7489b98176..662f1e722991 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-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,6 +29,15 @@
* Subclass of {@link ServletRequestDataBinder} that adds URI template variables
* to the values used for data binding.
*
+ * WARNING: Data binding can lead to security issues by exposing
+ * parts of the object graph that are not meant to be accessed or modified by
+ * external clients. Therefore the design and use of data binding should be considered
+ * carefully with regard to security. For more details, please refer to the dedicated
+ * sections on data binding for
+ * Spring Web MVC and
+ * Spring WebFlux
+ * in the reference manual.
+ *
* @author Rossen Stoyanchev
* @since 3.1
* @see ServletRequestDataBinder
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 aeaf049adb23..2b9d7125d7d7 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -2236,6 +2236,7 @@ void routerFunction() throws ServletException, IOException {
assertThat(response.getContentAsString()).isEqualTo("foo-body");
}
+
@Controller
static class ControllerWithEmptyValueMapping {
@@ -3573,7 +3574,6 @@ public void httpHeaders(@RequestHeader HttpHeaders headers, Writer writer) throw
assertThat(headers.getContentType()).as("Invalid Content-Type").isEqualTo(new MediaType("text", "html"));
multiValueMap(headers, writer);
}
-
}
@Controller
diff --git a/src/docs/asciidoc/web/web-data-binding-model-design.adoc b/src/docs/asciidoc/web/web-data-binding-model-design.adoc
new file mode 100644
index 000000000000..352e63d3c6f3
--- /dev/null
+++ b/src/docs/asciidoc/web/web-data-binding-model-design.adoc
@@ -0,0 +1,95 @@
+In the context of web applications, _data binding_ involves the binding of HTTP request
+parameters (that is, form data or query parameters) to properties in a model object and
+its nested objects.
+
+Only `public` properties following the
+https://www.oracle.com/java/technologies/javase/javabeans-spec.html[JavaBeans naming conventions]
+are exposed for data binding — for example, `public String getFirstName()` and
+`public void setFirstName(String)` methods for a `firstName` property.
+
+TIP: The model object, and its nested object graph, is also sometimes referred to as a
+_command object_, _form-backing object_, or _POJO_ (Plain Old Java Object).
+
+By default, Spring permits binding to all public properties in the model object graph.
+This means you need to carefully consider what public properties the model has, since a
+client could target any public property path, even some that are not expected to be
+targeted for a given use case.
+
+For example, given an HTTP form data endpoint, a malicious client could supply values for
+properties that exist in the model object graph but are not part of the HTML form
+presented in the browser. This could lead to data being set on the model object and any
+of its nested objects, that is not expected to be updated.
+
+The recommended approach is to use a _dedicated model object_ that exposes only
+properties that are relevant for the form submission. For example, on a form for changing
+a user's email address, the model object should declare a minimum set of properties such
+as in the following `ChangeEmailForm`.
+
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+ public class ChangeEmailForm {
+
+ private String oldEmailAddress;
+ private String newEmailAddress;
+
+ public void setOldEmailAddress(String oldEmailAddress) {
+ this.oldEmailAddress = oldEmailAddress;
+ }
+
+ public String getOldEmailAddress() {
+ return this.oldEmailAddress;
+ }
+
+ public void setNewEmailAddress(String newEmailAddress) {
+ this.newEmailAddress = newEmailAddress;
+ }
+
+ public String getNewEmailAddress() {
+ return this.newEmailAddress;
+ }
+
+ }
+----
+
+If you cannot or do not want to use a _dedicated model object_ for each data
+binding use case, you **must** limit the properties that are allowed for data binding.
+Ideally, you can achieve this by registering _allowed field patterns_ via the
+`setAllowedFields()` method on `WebDataBinder`.
+
+For example, to register allowed field patterns in your application, you can implement an
+`@InitBinder` method in a `@Controller` or `@ControllerAdvice` component as shown below:
+
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+ @Controller
+ public class ChangeEmailController {
+
+ @InitBinder
+ void initBinder(WebDataBinder binder) {
+ binder.setAllowedFields("oldEmailAddress", "newEmailAddress");
+ }
+
+ // @RequestMapping methods, etc.
+
+ }
+----
+
+In addition to registering allowed patterns, it is also possible to register _disallowed
+field patterns_ via the `setDisallowedFields()` method in `DataBinder` and its subclasses.
+Please note, however, that an "allow list" is safer than a "deny list". Consequently,
+`setAllowedFields()` should be favored over `setDisallowedFields()`.
+
+Note that matching against allowed field patterns is case-sensitive; whereas, matching
+against disallowed field patterns is case-insensitive. In addition, a field matching a
+disallowed pattern will not be accepted even if it also happens to match a pattern in the
+allowed list.
+
+[WARNING]
+====
+It is extremely important to properly configure allowed and disallowed field patterns
+when exposing your domain model directly for data binding purposes. Otherwise, it is a
+big security risk.
+
+Furthermore, it is strongly recommended that you do **not** use types from your domain
+model such as JPA or Hibernate entities as the model object in data binding scenarios.
+====
diff --git a/src/docs/asciidoc/web/webflux.adoc b/src/docs/asciidoc/web/webflux.adoc
index 111980c64616..2276f15aadf9 100644
--- a/src/docs/asciidoc/web/webflux.adoc
+++ b/src/docs/asciidoc/web/webflux.adoc
@@ -3319,6 +3319,11 @@ controller-specific `Formatter` instances, as the following example shows:
----
<1> Adding a custom formatter (a `DateFormatter`, in this case).
+[[webflux-ann-initbinder-model-design]]
+==== Model Design
+[.small]#<>#
+
+include::web-data-binding-model-design.adoc[]
[[webflux-ann-controller-exceptions]]
diff --git a/src/docs/asciidoc/web/webmvc.adoc b/src/docs/asciidoc/web/webmvc.adoc
index 705c33a5d2a7..30a3867b00b6 100644
--- a/src/docs/asciidoc/web/webmvc.adoc
+++ b/src/docs/asciidoc/web/webmvc.adoc
@@ -3751,6 +3751,13 @@ controller-specific `Formatter` implementations, as the following example shows:
----
<1> Defining an `@InitBinder` method on a custom formatter.
+[[mvc-ann-initbinder-model-design]]
+==== Model Design
+[.small]#<>#
+
+include::web-data-binding-model-design.adoc[]
+
+
[[mvc-ann-exceptionhandler]]
=== Exceptions
[.small]#<>#