diff --git a/build.gradle b/build.gradle
index 99f8ffb540b2..06af26a95ad4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -28,7 +28,7 @@ configure(allprojects) { project ->
dependencyManagement {
imports {
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.7"
- mavenBom "io.netty:netty-bom:4.1.94.Final"
+ mavenBom "io.netty:netty-bom:4.1.95.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.34"
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR13"
mavenBom "io.rsocket:rsocket-bom:1.1.3"
diff --git a/gradle.properties b/gradle.properties
index 6089d4d958d3..9ae12b8dfed3 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,4 @@
-version=5.3.29-SNAPSHOT
+version=5.3.30
org.gradle.jvmargs=-Xmx2048m
org.gradle.caching=true
org.gradle.parallel=true
diff --git a/integration-tests/src/test/java/org/springframework/scheduling/annotation/ScheduledAndTransactionalAnnotationIntegrationTests.java b/integration-tests/src/test/java/org/springframework/scheduling/annotation/ScheduledAndTransactionalAnnotationIntegrationTests.java
index 9062d6e3f615..be62d81ee013 100644
--- a/integration-tests/src/test/java/org/springframework/scheduling/annotation/ScheduledAndTransactionalAnnotationIntegrationTests.java
+++ b/integration-tests/src/test/java/org/springframework/scheduling/annotation/ScheduledAndTransactionalAnnotationIntegrationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author 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 @@ void failsWhenJdkProxyAndScheduledMethodNotPresentOnInterface() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(Config.class, JdkProxyTxConfig.class, RepoConfigA.class);
assertThatExceptionOfType(BeanCreationException.class)
- .isThrownBy(ctx::refresh)
- .withCauseInstanceOf(IllegalStateException.class);
+ .isThrownBy(ctx::refresh)
+ .withCauseInstanceOf(IllegalStateException.class);
}
@Test
@@ -70,7 +70,7 @@ void succeedsWhenSubclassProxyAndScheduledMethodNotPresentOnInterface() throws I
ctx.register(Config.class, SubclassProxyTxConfig.class, RepoConfigA.class);
ctx.refresh();
- Thread.sleep(100); // allow @Scheduled method to be called several times
+ Thread.sleep(200); // allow @Scheduled method to be called several times
MyRepository repository = ctx.getBean(MyRepository.class);
CallCountingTransactionManager txManager = ctx.getBean(CallCountingTransactionManager.class);
@@ -85,7 +85,7 @@ void succeedsWhenJdkProxyAndScheduledMethodIsPresentOnInterface() throws Interru
ctx.register(Config.class, JdkProxyTxConfig.class, RepoConfigB.class);
ctx.refresh();
- Thread.sleep(100); // allow @Scheduled method to be called several times
+ Thread.sleep(200); // allow @Scheduled method to be called several times
MyRepositoryWithScheduledMethod repository = ctx.getBean(MyRepositoryWithScheduledMethod.class);
CallCountingTransactionManager txManager = ctx.getBean(CallCountingTransactionManager.class);
@@ -100,7 +100,7 @@ void withAspectConfig() throws InterruptedException {
ctx.register(AspectConfig.class, MyRepositoryWithScheduledMethodImpl.class);
ctx.refresh();
- Thread.sleep(100); // allow @Scheduled method to be called several times
+ Thread.sleep(200); // allow @Scheduled method to be called several times
MyRepositoryWithScheduledMethod repository = ctx.getBean(MyRepositoryWithScheduledMethod.class);
assertThat(AopUtils.isCglibProxy(repository)).isTrue();
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java
index b2efee6caa2c..5c79bb5aa703 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -248,8 +248,8 @@ private PointcutParser initializePointcutParser(@Nullable ClassLoader classLoade
/**
* If a pointcut expression has been specified in XML, the user cannot
- * write {@code and} as "&&" (though && will work).
- * We also allow {@code and} between two pointcut sub-expressions.
+ * write "and" as "&&" (though {@code &&} will work).
+ *
We also allow "and" between two pointcut sub-expressions.
*
This method converts back to {@code &&} for the AspectJ pointcut parser.
*/
private String replaceBooleanOperators(String pcExpr) {
@@ -527,7 +527,7 @@ public boolean equals(@Nullable Object other) {
return false;
}
AspectJExpressionPointcut otherPc = (AspectJExpressionPointcut) other;
- return ObjectUtils.nullSafeEquals(this.getExpression(), otherPc.getExpression()) &&
+ return ObjectUtils.nullSafeEquals(getExpression(), otherPc.getExpression()) &&
ObjectUtils.nullSafeEquals(this.pointcutDeclarationScope, otherPc.pointcutDeclarationScope) &&
ObjectUtils.nullSafeEquals(this.pointcutParameterNames, otherPc.pointcutParameterNames) &&
ObjectUtils.nullSafeEquals(this.pointcutParameterTypes, otherPc.pointcutParameterTypes);
@@ -535,7 +535,7 @@ public boolean equals(@Nullable Object other) {
@Override
public int hashCode() {
- int hashCode = ObjectUtils.nullSafeHashCode(this.getExpression());
+ int hashCode = ObjectUtils.nullSafeHashCode(getExpression());
hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.pointcutDeclarationScope);
hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.pointcutParameterNames);
hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.pointcutParameterTypes);
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 1af6ae029aaf..a172c86c2472 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
@@ -424,10 +424,9 @@ public static class SerializableNoOp implements NoOp, Serializable {
/**
- * Method interceptor used for static targets with no advice chain. The call
- * is passed directly back to the target. Used when the proxy needs to be
- * exposed and it can't be determined that the method won't return
- * {@code this}.
+ * Method interceptor used for static targets with no advice chain. The call is
+ * passed directly back to the target. Used when the proxy needs to be exposed
+ * and it can't be determined that the method won't return {@code this}.
*/
private static class StaticUnadvisedInterceptor implements MethodInterceptor, Serializable {
diff --git a/spring-aop/src/test/java/org/springframework/aop/framework/adapter/ThrowsAdviceInterceptorTests.java b/spring-aop/src/test/java/org/springframework/aop/framework/adapter/ThrowsAdviceInterceptorTests.java
index 40b038b457ef..d1bfc13af5d5 100644
--- a/spring-aop/src/test/java/org/springframework/aop/framework/adapter/ThrowsAdviceInterceptorTests.java
+++ b/spring-aop/src/test/java/org/springframework/aop/framework/adapter/ThrowsAdviceInterceptorTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -77,9 +77,7 @@ public void testCorrectHandlerUsed() throws Throwable {
given(mi.getMethod()).willReturn(Object.class.getMethod("hashCode"));
given(mi.getThis()).willReturn(new Object());
given(mi.proceed()).willThrow(ex);
- assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() ->
- ti.invoke(mi))
- .isSameAs(ex);
+ assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() -> ti.invoke(mi)).isSameAs(ex);
assertThat(th.getCalls()).isEqualTo(1);
assertThat(th.getCalls("ioException")).isEqualTo(1);
}
@@ -92,9 +90,7 @@ public void testCorrectHandlerUsedForSubclass() throws Throwable {
ConnectException ex = new ConnectException("");
MethodInvocation mi = mock(MethodInvocation.class);
given(mi.proceed()).willThrow(ex);
- assertThatExceptionOfType(ConnectException.class).isThrownBy(() ->
- ti.invoke(mi))
- .isSameAs(ex);
+ assertThatExceptionOfType(ConnectException.class).isThrownBy(() -> ti.invoke(mi)).isSameAs(ex);
assertThat(th.getCalls()).isEqualTo(1);
assertThat(th.getCalls("remoteException")).isEqualTo(1);
}
@@ -117,9 +113,7 @@ public void afterThrowing(RemoteException ex) throws Throwable {
ConnectException ex = new ConnectException("");
MethodInvocation mi = mock(MethodInvocation.class);
given(mi.proceed()).willThrow(ex);
- assertThatExceptionOfType(Throwable.class).isThrownBy(() ->
- ti.invoke(mi))
- .isSameAs(t);
+ assertThatExceptionOfType(Throwable.class).isThrownBy(() -> ti.invoke(mi)).isSameAs(t);
assertThat(th.getCalls()).isEqualTo(1);
assertThat(th.getCalls("remoteException")).isEqualTo(1);
}
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 42e9acca27f1..1af0bde135c6 100644
--- a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java
+++ b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -248,9 +248,22 @@ private static BeanInfo getBeanInfo(Class> beanClass) throws IntrospectionExce
return beanInfo;
}
}
- return (shouldIntrospectorIgnoreBeaninfoClasses ?
+
+ BeanInfo beanInfo = (shouldIntrospectorIgnoreBeaninfoClasses ?
Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) :
Introspector.getBeanInfo(beanClass));
+
+ // Immediately remove class from Introspector cache to allow for proper garbage
+ // collection on class loader shutdown; we cache it in CachedIntrospectionResults
+ // in a GC-friendly manner. This is necessary (again) for the JDK ClassInfo cache.
+ Class> classToFlush = beanClass;
+ do {
+ Introspector.flushFromCaches(classToFlush);
+ classToFlush = classToFlush.getSuperclass();
+ }
+ while (classToFlush != null && classToFlush != Object.class);
+
+ return beanInfo;
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java
index d77880c012c6..95fa55766032 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java
@@ -17,6 +17,7 @@
package org.springframework.beans.factory.annotation;
import java.beans.PropertyDescriptor;
+import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
@@ -62,6 +63,10 @@
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.core.type.MethodMetadata;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -144,6 +149,9 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
@Nullable
private ConfigurableListableBeanFactory beanFactory;
+ @Nullable
+ private MetadataReaderFactory metadataReaderFactory;
+
private final Set lookupMethodsChecked = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
private final Map, Constructor>[]> candidateConstructorsCache = new ConcurrentHashMap<>(256);
@@ -238,6 +246,7 @@ public void setBeanFactory(BeanFactory beanFactory) {
"AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
}
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
+ this.metadataReaderFactory = new SimpleMetadataReaderFactory(this.beanFactory.getBeanClassLoader());
}
@@ -463,12 +472,11 @@ private InjectionMetadata buildAutowiringMetadata(Class> clazz) {
return InjectionMetadata.EMPTY;
}
- List elements = new ArrayList<>();
+ final List elements = new ArrayList<>();
Class> targetClass = clazz;
do {
- final List currElements = new ArrayList<>();
-
+ final List fieldElements = new ArrayList<>();
ReflectionUtils.doWithLocalFields(targetClass, field -> {
MergedAnnotation> ann = findAutowiredAnnotation(field);
if (ann != null) {
@@ -479,10 +487,11 @@ private InjectionMetadata buildAutowiringMetadata(Class> clazz) {
return;
}
boolean required = determineRequiredStatus(ann);
- currElements.add(new AutowiredFieldElement(field, required));
+ fieldElements.add(new AutowiredFieldElement(field, required));
}
});
+ final List methodElements = new ArrayList<>();
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
@@ -504,11 +513,12 @@ private InjectionMetadata buildAutowiringMetadata(Class> clazz) {
}
boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
- currElements.add(new AutowiredMethodElement(method, required, pd));
+ methodElements.add(new AutowiredMethodElement(method, required, pd));
}
});
- elements.addAll(0, currElements);
+ elements.addAll(0, sortMethodElements(methodElements, targetClass));
+ elements.addAll(0, fieldElements);
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
@@ -573,6 +583,47 @@ protected Map findAutowireCandidates(Class type) throws BeansE
return BeanFactoryUtils.beansOfTypeIncludingAncestors(this.beanFactory, type);
}
+ /**
+ * Sort the method elements via ASM for deterministic declaration order if possible.
+ */
+ private List sortMethodElements(
+ List methodElements, Class> targetClass) {
+
+ if (this.metadataReaderFactory != null && methodElements.size() > 1) {
+ // Try reading the class file via ASM for deterministic declaration order...
+ // Unfortunately, the JVM's standard reflection returns methods in arbitrary
+ // order, even between different runs of the same application on the same JVM.
+ try {
+ AnnotationMetadata asm =
+ this.metadataReaderFactory.getMetadataReader(targetClass.getName()).getAnnotationMetadata();
+ Set asmMethods = asm.getAnnotatedMethods(Autowired.class.getName());
+ if (asmMethods.size() >= methodElements.size()) {
+ List candidateMethods = new ArrayList<>(methodElements);
+ List selectedMethods = new ArrayList<>(asmMethods.size());
+ for (MethodMetadata asmMethod : asmMethods) {
+ for (Iterator it = candidateMethods.iterator(); it.hasNext();) {
+ InjectionMetadata.InjectedElement element = it.next();
+ if (element.getMember().getName().equals(asmMethod.getMethodName())) {
+ selectedMethods.add(element);
+ it.remove();
+ break;
+ }
+ }
+ }
+ if (selectedMethods.size() == methodElements.size()) {
+ // All reflection-detected methods found in ASM method set -> proceed
+ return selectedMethods;
+ }
+ }
+ }
+ catch (IOException ex) {
+ logger.debug("Failed to read class file via ASM for determining @Autowired method order", ex);
+ // No worries, let's continue with the reflection metadata we started with...
+ }
+ }
+ return methodElements;
+ }
+
/**
* Register the specified bean as dependent on the autowired beans.
*/
@@ -651,7 +702,7 @@ protected void inject(Object bean, @Nullable String beanName, @Nullable Property
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
- Set autowiredBeanNames = new LinkedHashSet<>(1);
+ Set autowiredBeanNames = new LinkedHashSet<>(2);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
Object value;
@@ -670,8 +721,7 @@ private Object resolveFieldValue(Field field, Object bean, @Nullable String bean
String autowiredBeanName = autowiredBeanNames.iterator().next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
- cachedFieldValue = new ShortcutDependencyDescriptor(
- desc, autowiredBeanName, field.getType());
+ cachedFieldValue = new ShortcutDependencyDescriptor(desc, autowiredBeanName);
}
}
this.cachedFieldValue = cachedFieldValue;
@@ -754,7 +804,7 @@ private Object[] resolveMethodArguments(Method method, Object bean, @Nullable St
int argumentCount = method.getParameterCount();
Object[] arguments = new Object[argumentCount];
DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount];
- Set autowiredBeans = new LinkedHashSet<>(argumentCount);
+ Set autowiredBeanNames = new LinkedHashSet<>(argumentCount * 2);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
for (int i = 0; i < arguments.length; i++) {
@@ -763,7 +813,7 @@ private Object[] resolveMethodArguments(Method method, Object bean, @Nullable St
currDesc.setContainingClass(bean.getClass());
descriptors[i] = currDesc;
try {
- Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
+ Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeanNames, typeConverter);
if (arg == null && !this.required) {
arguments = null;
break;
@@ -778,16 +828,16 @@ private Object[] resolveMethodArguments(Method method, Object bean, @Nullable St
if (!this.cached) {
if (arguments != null) {
DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, argumentCount);
- registerDependentBeans(beanName, autowiredBeans);
- if (autowiredBeans.size() == argumentCount) {
- Iterator it = autowiredBeans.iterator();
+ registerDependentBeans(beanName, autowiredBeanNames);
+ if (autowiredBeanNames.size() == argumentCount) {
+ Iterator it = autowiredBeanNames.iterator();
Class>[] paramTypes = method.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
String autowiredBeanName = it.next();
if (arguments[i] != null && beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
cachedMethodArguments[i] = new ShortcutDependencyDescriptor(
- descriptors[i], autowiredBeanName, paramTypes[i]);
+ descriptors[i], autowiredBeanName);
}
}
}
@@ -813,17 +863,14 @@ private static class ShortcutDependencyDescriptor extends DependencyDescriptor {
private final String shortcut;
- private final Class> requiredType;
-
- public ShortcutDependencyDescriptor(DependencyDescriptor original, String shortcut, Class> requiredType) {
+ public ShortcutDependencyDescriptor(DependencyDescriptor original, String shortcut) {
super(original);
this.shortcut = shortcut;
- this.requiredType = requiredType;
}
@Override
public Object resolveShortcut(BeanFactory beanFactory) {
- return beanFactory.getBean(this.shortcut, this.requiredType);
+ return beanFactory.getBean(this.shortcut, getDependencyType());
}
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java
index fcb2ef67e36a..fb0ba14b0be4 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1504,8 +1504,8 @@ protected void autowireByType(
converter = bw;
}
- Set autowiredBeanNames = new LinkedHashSet<>(4);
String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
+ Set autowiredBeanNames = new LinkedHashSet<>(propertyNames.length * 2);
for (String propertyName : propertyNames) {
try {
PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java
index be9667b19a38..a15ebb5a1ab4 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -134,7 +134,7 @@ else if (value instanceof BeanDefinition) {
return resolveInnerBean(argName, innerBeanName, bd);
}
else if (value instanceof DependencyDescriptor) {
- Set autowiredBeanNames = new LinkedHashSet<>(4);
+ Set autowiredBeanNames = new LinkedHashSet<>(2);
Object result = this.beanFactory.resolveDependency(
(DependencyDescriptor) value, this.beanName, autowiredBeanNames, this.typeConverter);
for (String autowiredBeanName : autowiredBeanNames) {
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java
index dd268fc517fe..a30075f27318 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,6 +45,7 @@
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
+import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
@@ -85,12 +86,6 @@ class ConstructorResolver {
private static final Object[] EMPTY_ARGS = new Object[0];
- /**
- * Marker for autowired arguments in a cached argument array, to be replaced
- * by a {@linkplain #resolveAutowiredArgument resolved autowired argument}.
- */
- private static final Object autowiredArgumentMarker = new Object();
-
private static final NamedThreadLocal currentInjectionPoint =
new NamedThreadLocal<>("Current injection point");
@@ -729,7 +724,7 @@ private ArgumentsHolder createArgumentArray(
ArgumentsHolder args = new ArgumentsHolder(paramTypes.length);
Set usedValueHolders = new HashSet<>(paramTypes.length);
- Set autowiredBeanNames = new LinkedHashSet<>(4);
+ Set allAutowiredBeanNames = new LinkedHashSet<>(paramTypes.length * 2);
for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {
Class> paramType = paramTypes[paramIndex];
@@ -764,8 +759,8 @@ private ArgumentsHolder createArgumentArray(
throw new UnsatisfiedDependencyException(
mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
"Could not convert argument value of type [" +
- ObjectUtils.nullSafeClassName(valueHolder.getValue()) +
- "] to required type [" + paramType.getName() + "]: " + ex.getMessage());
+ ObjectUtils.nullSafeClassName(valueHolder.getValue()) +
+ "] to required type [" + paramType.getName() + "]: " + ex.getMessage());
}
Object sourceHolder = valueHolder.getSource();
if (sourceHolder instanceof ConstructorArgumentValues.ValueHolder) {
@@ -788,11 +783,17 @@ private ArgumentsHolder createArgumentArray(
"] - did you specify the correct bean references as arguments?");
}
try {
- Object autowiredArgument = resolveAutowiredArgument(
- methodParam, beanName, autowiredBeanNames, converter, fallback);
- args.rawArguments[paramIndex] = autowiredArgument;
- args.arguments[paramIndex] = autowiredArgument;
- args.preparedArguments[paramIndex] = autowiredArgumentMarker;
+ ConstructorDependencyDescriptor desc = new ConstructorDependencyDescriptor(methodParam, true);
+ Set autowiredBeanNames = new LinkedHashSet<>(2);
+ Object arg = resolveAutowiredArgument(
+ desc, paramType, beanName, autowiredBeanNames, converter, fallback);
+ if (arg != null) {
+ setShortcutIfPossible(desc, paramType, autowiredBeanNames);
+ }
+ allAutowiredBeanNames.addAll(autowiredBeanNames);
+ args.rawArguments[paramIndex] = arg;
+ args.arguments[paramIndex] = arg;
+ args.preparedArguments[paramIndex] = desc;
args.resolveNecessary = true;
}
catch (BeansException ex) {
@@ -802,14 +803,7 @@ private ArgumentsHolder createArgumentArray(
}
}
- for (String autowiredBeanName : autowiredBeanNames) {
- this.beanFactory.registerDependentBean(autowiredBeanName, beanName);
- if (logger.isDebugEnabled()) {
- logger.debug("Autowiring by type from bean name '" + beanName +
- "' via " + (executable instanceof Constructor ? "constructor" : "factory method") +
- " to bean named '" + autowiredBeanName + "'");
- }
- }
+ registerDependentBeans(executable, beanName, allAutowiredBeanNames);
return args;
}
@@ -829,31 +823,61 @@ private Object[] resolvePreparedArguments(String beanName, RootBeanDefinition mb
Object[] resolvedArgs = new Object[argsToResolve.length];
for (int argIndex = 0; argIndex < argsToResolve.length; argIndex++) {
Object argValue = argsToResolve[argIndex];
- MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex);
- if (argValue == autowiredArgumentMarker) {
- argValue = resolveAutowiredArgument(methodParam, beanName, null, converter, true);
+ Class> paramType = paramTypes[argIndex];
+ boolean convertNecessary = false;
+ if (argValue instanceof ConstructorDependencyDescriptor) {
+ ConstructorDependencyDescriptor descriptor = (ConstructorDependencyDescriptor) argValue;
+ try {
+ argValue = resolveAutowiredArgument(descriptor, paramType, beanName,
+ null, converter, true);
+ }
+ catch (BeansException ex) {
+ // Unexpected target bean mismatch for cached argument -> re-resolve
+ Set autowiredBeanNames = null;
+ if (descriptor.hasShortcut()) {
+ // Reset shortcut and try to re-resolve it in this thread...
+ descriptor.setShortcut(null);
+ autowiredBeanNames = new LinkedHashSet<>(2);
+ }
+ logger.debug("Failed to resolve cached argument", ex);
+ argValue = resolveAutowiredArgument(descriptor, paramType, beanName,
+ autowiredBeanNames, converter, true);
+ if (autowiredBeanNames != null && !descriptor.hasShortcut()) {
+ // We encountered as stale shortcut before, and the shortcut has
+ // not been re-resolved by another thread in the meantime...
+ if (argValue != null) {
+ setShortcutIfPossible(descriptor, paramType, autowiredBeanNames);
+ }
+ registerDependentBeans(executable, beanName, autowiredBeanNames);
+ }
+ }
}
else if (argValue instanceof BeanMetadataElement) {
argValue = valueResolver.resolveValueIfNecessary("constructor argument", argValue);
+ convertNecessary = true;
}
else if (argValue instanceof String) {
argValue = this.beanFactory.evaluateBeanDefinitionString((String) argValue, mbd);
+ convertNecessary = true;
}
- Class> paramType = paramTypes[argIndex];
- try {
- resolvedArgs[argIndex] = converter.convertIfNecessary(argValue, paramType, methodParam);
- }
- catch (TypeMismatchException ex) {
- throw new UnsatisfiedDependencyException(
- mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
- "Could not convert argument value of type [" + ObjectUtils.nullSafeClassName(argValue) +
- "] to required type [" + paramType.getName() + "]: " + ex.getMessage());
+ if (convertNecessary) {
+ MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex);
+ try {
+ argValue = converter.convertIfNecessary(argValue, paramType, methodParam);
+ }
+ catch (TypeMismatchException ex) {
+ throw new UnsatisfiedDependencyException(
+ mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
+ "Could not convert argument value of type [" + ObjectUtils.nullSafeClassName(argValue) +
+ "] to required type [" + paramType.getName() + "]: " + ex.getMessage());
+ }
}
+ resolvedArgs[argIndex] = argValue;
}
return resolvedArgs;
}
- protected Constructor> getUserDeclaredConstructor(Constructor> constructor) {
+ private Constructor> getUserDeclaredConstructor(Constructor> constructor) {
Class> declaringClass = constructor.getDeclaringClass();
Class> userClass = ClassUtils.getUserClass(declaringClass);
if (userClass != declaringClass) {
@@ -869,23 +893,22 @@ protected Constructor> getUserDeclaredConstructor(Constructor> constructor)
}
/**
- * Template method for resolving the specified argument which is supposed to be autowired.
+ * Resolve the specified argument which is supposed to be autowired.
*/
@Nullable
- protected Object resolveAutowiredArgument(MethodParameter param, String beanName,
+ Object resolveAutowiredArgument(DependencyDescriptor descriptor, Class> paramType, String beanName,
@Nullable Set autowiredBeanNames, TypeConverter typeConverter, boolean fallback) {
- Class> paramType = param.getParameterType();
if (InjectionPoint.class.isAssignableFrom(paramType)) {
InjectionPoint injectionPoint = currentInjectionPoint.get();
if (injectionPoint == null) {
- throw new IllegalStateException("No current InjectionPoint available for " + param);
+ throw new IllegalStateException("No current InjectionPoint available for " + descriptor);
}
return injectionPoint;
}
+
try {
- return this.beanFactory.resolveDependency(
- new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter);
+ return this.beanFactory.resolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter);
}
catch (NoUniqueBeanDefinitionException ex) {
throw ex;
@@ -908,6 +931,31 @@ else if (CollectionFactory.isApproximableMapType(paramType)) {
}
}
+ private void setShortcutIfPossible(
+ ConstructorDependencyDescriptor descriptor, Class> paramType, Set autowiredBeanNames) {
+
+ if (autowiredBeanNames.size() == 1) {
+ String autowiredBeanName = autowiredBeanNames.iterator().next();
+ if (this.beanFactory.containsBean(autowiredBeanName) &&
+ this.beanFactory.isTypeMatch(autowiredBeanName, paramType)) {
+ descriptor.setShortcut(autowiredBeanName);
+ }
+ }
+ }
+
+ private void registerDependentBeans(
+ Executable executable, String beanName, Set autowiredBeanNames) {
+
+ for (String autowiredBeanName : autowiredBeanNames) {
+ this.beanFactory.registerDependentBean(autowiredBeanName, beanName);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Autowiring by type from bean name '" + beanName + "' via " +
+ (executable instanceof Constructor ? "constructor" : "factory method") +
+ " to bean named '" + autowiredBeanName + "'");
+ }
+ }
+ }
+
static InjectionPoint setCurrentInjectionPoint(@Nullable InjectionPoint injectionPoint) {
InjectionPoint old = currentInjectionPoint.get();
if (injectionPoint != null) {
@@ -1006,4 +1054,35 @@ public static String[] evaluate(Constructor> candidate, int paramCount) {
}
}
+
+ /**
+ * DependencyDescriptor marker for constructor arguments,
+ * for differentiating between a provided DependencyDescriptor instance
+ * and an internally built DependencyDescriptor for autowiring purposes.
+ */
+ @SuppressWarnings("serial")
+ private static class ConstructorDependencyDescriptor extends DependencyDescriptor {
+
+ @Nullable
+ private volatile String shortcut;
+
+ public ConstructorDependencyDescriptor(MethodParameter methodParameter, boolean required) {
+ super(methodParameter, required);
+ }
+
+ public void setShortcut(@Nullable String shortcut) {
+ this.shortcut = shortcut;
+ }
+
+ public boolean hasShortcut() {
+ return (this.shortcut != null);
+ }
+
+ @Override
+ public Object resolveShortcut(BeanFactory beanFactory) {
+ String shortcut = this.shortcut;
+ return (shortcut != null ? beanFactory.getBean(shortcut, getDependencyType()) : null);
+ }
+ }
+
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java
index c441bf7e9cc3..f24001330206 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -112,7 +112,7 @@ public boolean equals(@Nullable Object other) {
@Override
public int hashCode() {
- return (29 * super.hashCode() + ObjectUtils.nullSafeHashCode(this.beanName));
+ return super.hashCode() * 29 + ObjectUtils.nullSafeHashCode(this.beanName);
}
@Override
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java
index 45a4bb48c7bf..0c227674fd75 100644
--- a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java
@@ -1271,10 +1271,11 @@ void autowireWithTwoMatchesForConstructorDependency() {
lbf.registerBeanDefinition("rod", bd);
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
lbf.registerBeanDefinition("rod2", bd2);
- assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(() ->
- lbf.autowire(ConstructorDependency.class, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false))
- .withMessageContaining("rod")
- .withMessageContaining("rod2");
+
+ assertThatExceptionOfType(UnsatisfiedDependencyException.class)
+ .isThrownBy(() -> lbf.autowire(ConstructorDependency.class, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false))
+ .withMessageContaining("rod")
+ .withMessageContaining("rod2");
}
@Test
@@ -1336,11 +1337,12 @@ void dependsOnCycle() {
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
bd2.setDependsOn("tb1");
lbf.registerBeanDefinition("tb2", bd2);
- assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
- lbf.preInstantiateSingletons())
- .withMessageContaining("Circular")
- .withMessageContaining("'tb2'")
- .withMessageContaining("'tb1'");
+
+ assertThatExceptionOfType(BeanCreationException.class)
+ .isThrownBy(() -> lbf.preInstantiateSingletons())
+ .withMessageContaining("Circular")
+ .withMessageContaining("'tb2'")
+ .withMessageContaining("'tb1'");
}
@Test
@@ -1354,11 +1356,12 @@ void implicitDependsOnCycle() {
RootBeanDefinition bd3 = new RootBeanDefinition(TestBean.class);
bd3.setDependsOn("tb1");
lbf.registerBeanDefinition("tb3", bd3);
- assertThatExceptionOfType(BeanCreationException.class).isThrownBy(
- lbf::preInstantiateSingletons)
- .withMessageContaining("Circular")
- .withMessageContaining("'tb3'")
- .withMessageContaining("'tb1'");
+
+ assertThatExceptionOfType(BeanCreationException.class)
+ .isThrownBy(lbf::preInstantiateSingletons)
+ .withMessageContaining("Circular")
+ .withMessageContaining("'tb3'")
+ .withMessageContaining("'tb1'");
}
@Test
@@ -1493,10 +1496,11 @@ void getBeanByTypeWithMultiplePriority() {
RootBeanDefinition bd2 = new RootBeanDefinition(HighPriorityTestBean.class);
lbf.registerBeanDefinition("bd1", bd1);
lbf.registerBeanDefinition("bd2", bd2);
- assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(() ->
- lbf.getBean(TestBean.class))
- .withMessageContaining("Multiple beans found with the same priority")
- .withMessageContaining("5"); // conflicting priority
+
+ assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
+ .isThrownBy(() -> lbf.getBean(TestBean.class))
+ .withMessageContaining("Multiple beans found with the same priority")
+ .withMessageContaining("5"); // conflicting priority
}
@Test
@@ -1698,9 +1702,9 @@ void getBeanByTypeInstanceWithMultiplePrimary() {
lbf.registerBeanDefinition("bd1", bd1);
lbf.registerBeanDefinition("bd2", bd2);
- assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(() ->
- lbf.getBean(ConstructorDependency.class, 42))
- .withMessageContaining("more than one 'primary'");
+ assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
+ .isThrownBy(() -> lbf.getBean(ConstructorDependency.class, 42))
+ .withMessageContaining("more than one 'primary'");
}
@Test
@@ -1881,10 +1885,11 @@ void autowireBeanByTypeWithTwoMatches() {
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
lbf.registerBeanDefinition("test", bd);
lbf.registerBeanDefinition("spouse", bd2);
- assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(() ->
- lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true))
- .withMessageContaining("test")
- .withMessageContaining("spouse");
+
+ assertThatExceptionOfType(UnsatisfiedDependencyException.class)
+ .isThrownBy(() -> lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true))
+ .withMessageContaining("test")
+ .withMessageContaining("spouse");
}
@Test
@@ -1946,10 +1951,11 @@ void autowireBeanByTypeWithIdenticalPriorityCandidates() {
RootBeanDefinition bd2 = new RootBeanDefinition(HighPriorityTestBean.class);
lbf.registerBeanDefinition("test", bd);
lbf.registerBeanDefinition("spouse", bd2);
- assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(() ->
- lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true))
- .withCauseExactlyInstanceOf(NoUniqueBeanDefinitionException.class)
- .withMessageContaining("5");
+
+ assertThatExceptionOfType(UnsatisfiedDependencyException.class)
+ .isThrownBy(() -> lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true))
+ .withCauseExactlyInstanceOf(NoUniqueBeanDefinitionException.class)
+ .withMessageContaining("5");
}
@Test
@@ -2185,19 +2191,21 @@ void constructorDependencyWithUnresolvableClass() {
@Test
void beanDefinitionWithInterface() {
lbf.registerBeanDefinition("test", new RootBeanDefinition(ITestBean.class));
- assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
- lbf.getBean("test"))
- .withMessageContaining("interface")
- .satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("test"));
+
+ assertThatExceptionOfType(BeanCreationException.class)
+ .isThrownBy(() -> lbf.getBean("test"))
+ .withMessageContaining("interface")
+ .satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("test"));
}
@Test
void beanDefinitionWithAbstractClass() {
lbf.registerBeanDefinition("test", new RootBeanDefinition(AbstractBeanFactory.class));
- assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
- lbf.getBean("test"))
- .withMessageContaining("abstract")
- .satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("test"));
+
+ assertThatExceptionOfType(BeanCreationException.class)
+ .isThrownBy(() -> lbf.getBean("test"))
+ .withMessageContaining("abstract")
+ .satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("test"));
}
@Test
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java
index f3ec59432ddd..45f59fb1f689 100644
--- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java
@@ -72,6 +72,9 @@
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.Order;
import org.springframework.core.testfixture.io.SerializationTestUtils;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -131,6 +134,8 @@ public void testResourceInjection() {
bean = bf.getBean("annotatedBean", ResourceInjectionBean.class);
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
+
+ assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"});
}
@Test
@@ -152,10 +157,12 @@ public void testResourceInjectionWithNullBean() {
assertThat(bean.getTestBean()).isNull();
assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull();
+
+ assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"});
}
@Test
- void resourceInjectionWithSometimesNullBean() {
+ void resourceInjectionWithSometimesNullBeanEarly() {
RootBeanDefinition bd = new RootBeanDefinition(OptionalResourceInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
@@ -170,6 +177,18 @@ void resourceInjectionWithSometimesNullBean() {
assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull();
+ SometimesNullFactoryMethods.active = false;
+ bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
+ assertThat(bean.getTestBean()).isNull();
+ assertThat(bean.getTestBean2()).isNull();
+ assertThat(bean.getTestBean3()).isNull();
+
+ SometimesNullFactoryMethods.active = true;
+ bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
+ assertThat(bean.getTestBean()).isNotNull();
+ assertThat(bean.getTestBean2()).isNotNull();
+ assertThat(bean.getTestBean3()).isNotNull();
+
SometimesNullFactoryMethods.active = true;
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNotNull();
@@ -188,12 +207,43 @@ void resourceInjectionWithSometimesNullBean() {
assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull();
+ assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"});
+ }
+
+ @Test
+ void resourceInjectionWithSometimesNullBeanLate() {
+ RootBeanDefinition bd = new RootBeanDefinition(OptionalResourceInjectionBean.class);
+ bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
+ bf.registerBeanDefinition("annotatedBean", bd);
+ RootBeanDefinition tb = new RootBeanDefinition(SometimesNullFactoryMethods.class);
+ tb.setFactoryMethodName("createTestBean");
+ tb.setScope(BeanDefinition.SCOPE_PROTOTYPE);
+ bf.registerBeanDefinition("testBean", tb);
+
+ SometimesNullFactoryMethods.active = true;
+ OptionalResourceInjectionBean bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
+ assertThat(bean.getTestBean()).isNotNull();
+ assertThat(bean.getTestBean2()).isNotNull();
+ assertThat(bean.getTestBean3()).isNotNull();
+
SometimesNullFactoryMethods.active = true;
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNotNull();
assertThat(bean.getTestBean2()).isNotNull();
assertThat(bean.getTestBean3()).isNotNull();
+ SometimesNullFactoryMethods.active = false;
+ bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
+ assertThat(bean.getTestBean()).isNull();
+ assertThat(bean.getTestBean2()).isNull();
+ assertThat(bean.getTestBean3()).isNull();
+
+ SometimesNullFactoryMethods.active = false;
+ bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
+ assertThat(bean.getTestBean()).isNull();
+ assertThat(bean.getTestBean2()).isNull();
+ assertThat(bean.getTestBean3()).isNull();
+
SometimesNullFactoryMethods.active = true;
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNotNull();
@@ -205,6 +255,8 @@ void resourceInjectionWithSometimesNullBean() {
assertThat(bean.getTestBean()).isNull();
assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull();
+
+ assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"});
}
@Test
@@ -233,10 +285,7 @@ public void testExtendedResourceInjection() {
assertThat(bean.getNestedTestBean()).isSameAs(ntb);
assertThat(bean.getBeanFactory()).isSameAs(bf);
- String[] depBeans = bf.getDependenciesForBean("annotatedBean");
- assertThat(depBeans.length).isEqualTo(2);
- assertThat(depBeans[0]).isEqualTo("testBean");
- assertThat(depBeans[1]).isEqualTo("nestedTestBean");
+ assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean", "nestedTestBean"});
}
@Test
@@ -398,6 +447,22 @@ public void testOptionalResourceInjectionWithSingletonRemoval() {
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
bf.destroySingleton("testBean");
+ bf.registerSingleton("testBeanX", tb);
+
+ bean = bf.getBean("annotatedBean", OptionalResourceInjectionBean.class);
+ assertThat(bean.getTestBean()).isSameAs(tb);
+ assertThat(bean.getTestBean2()).isSameAs(tb);
+ assertThat(bean.getTestBean3()).isSameAs(tb);
+ assertThat(bean.getTestBean4()).isSameAs(tb);
+ assertThat(bean.getIndexedTestBean()).isSameAs(itb);
+ assertThat(bean.getNestedTestBeans()).hasSize(2);
+ assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
+ assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
+ assertThat(bean.nestedTestBeansField).hasSize(2);
+ assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
+ assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
+
+ bf.destroySingleton("testBeanX");
bean = bf.getBean("annotatedBean", OptionalResourceInjectionBean.class);
assertThat(bean.getTestBean()).isNull();
@@ -455,6 +520,22 @@ public void testOptionalResourceInjectionWithBeanDefinitionRemoval() {
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
bf.removeBeanDefinition("testBean");
+ bf.registerBeanDefinition("testBeanX", new RootBeanDefinition(TestBean.class));
+
+ bean = bf.getBean("annotatedBean", OptionalResourceInjectionBean.class);
+ assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBeanX"));
+ assertThat(bean.getTestBean2()).isSameAs(bf.getBean("testBeanX"));
+ assertThat(bean.getTestBean3()).isSameAs(bf.getBean("testBeanX"));
+ assertThat(bean.getTestBean4()).isSameAs(bf.getBean("testBeanX"));
+ assertThat(bean.getIndexedTestBean()).isSameAs(itb);
+ assertThat(bean.getNestedTestBeans()).hasSize(2);
+ assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
+ assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
+ assertThat(bean.nestedTestBeansField).hasSize(2);
+ assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
+ assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
+
+ bf.removeBeanDefinition("testBeanX");
bean = bf.getBean("annotatedBean", OptionalResourceInjectionBean.class);
assertThat(bean.getTestBean()).isNull();
@@ -719,6 +800,9 @@ public void testConstructorResourceInjection() {
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isSameAs(ntb);
assertThat(bean.getBeanFactory()).isSameAs(bf);
+
+ assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(
+ new String[] {"testBean", "nestedTestBean", ObjectUtils.identityToString(bf)});
}
@Test
@@ -740,6 +824,17 @@ public void testConstructorResourceInjectionWithSingletonRemoval() {
assertThat(bean.getBeanFactory()).isSameAs(bf);
bf.destroySingleton("nestedTestBean");
+ bf.registerSingleton("nestedTestBeanX", ntb);
+
+ bean = bf.getBean("annotatedBean", ConstructorResourceInjectionBean.class);
+ assertThat(bean.getTestBean()).isSameAs(tb);
+ assertThat(bean.getTestBean2()).isSameAs(tb);
+ assertThat(bean.getTestBean3()).isSameAs(tb);
+ assertThat(bean.getTestBean4()).isSameAs(tb);
+ assertThat(bean.getNestedTestBean()).isSameAs(ntb);
+ assertThat(bean.getBeanFactory()).isSameAs(bf);
+
+ bf.destroySingleton("nestedTestBeanX");
bean = bf.getBean("annotatedBean", ConstructorResourceInjectionBean.class);
assertThat(bean.getTestBean()).isSameAs(tb);
@@ -778,6 +873,17 @@ public void testConstructorResourceInjectionWithBeanDefinitionRemoval() {
assertThat(bean.getBeanFactory()).isSameAs(bf);
bf.removeBeanDefinition("nestedTestBean");
+ bf.registerBeanDefinition("nestedTestBeanX", new RootBeanDefinition(NestedTestBean.class));
+
+ bean = bf.getBean("annotatedBean", ConstructorResourceInjectionBean.class);
+ assertThat(bean.getTestBean()).isSameAs(tb);
+ assertThat(bean.getTestBean2()).isSameAs(tb);
+ assertThat(bean.getTestBean3()).isSameAs(tb);
+ assertThat(bean.getTestBean4()).isSameAs(tb);
+ assertThat(bean.getNestedTestBean()).isSameAs(bf.getBean("nestedTestBeanX"));
+ assertThat(bean.getBeanFactory()).isSameAs(bf);
+
+ bf.removeBeanDefinition("nestedTestBeanX");
bean = bf.getBean("annotatedBean", ConstructorResourceInjectionBean.class);
assertThat(bean.getTestBean()).isSameAs(tb);
@@ -881,6 +987,80 @@ public void testConstructorResourceInjectionWithNoCandidatesAndNoFallback() {
.satisfies(methodParameterDeclaredOn(ConstructorWithoutFallbackBean.class));
}
+ @Test
+ void constructorResourceInjectionWithSometimesNullBeanEarly() {
+ RootBeanDefinition bd = new RootBeanDefinition(ConstructorWithNullableArgument.class);
+ bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
+ bf.registerBeanDefinition("annotatedBean", bd);
+ RootBeanDefinition tb = new RootBeanDefinition(SometimesNullFactoryMethods.class);
+ tb.setFactoryMethodName("createTestBean");
+ tb.setScope(BeanDefinition.SCOPE_PROTOTYPE);
+ bf.registerBeanDefinition("testBean", tb);
+
+ SometimesNullFactoryMethods.active = false;
+ ConstructorWithNullableArgument bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
+ assertThat(bean.getTestBean3()).isNull();
+
+ SometimesNullFactoryMethods.active = false;
+ bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
+ assertThat(bean.getTestBean3()).isNull();
+
+ SometimesNullFactoryMethods.active = true;
+ bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
+ assertThat(bean.getTestBean3()).isNotNull();
+
+ SometimesNullFactoryMethods.active = true;
+ bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
+ assertThat(bean.getTestBean3()).isNotNull();
+
+ SometimesNullFactoryMethods.active = false;
+ bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
+ assertThat(bean.getTestBean3()).isNull();
+
+ SometimesNullFactoryMethods.active = false;
+ bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
+ assertThat(bean.getTestBean3()).isNull();
+
+ assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"});
+ }
+
+ @Test
+ void constructorResourceInjectionWithSometimesNullBeanLate() {
+ RootBeanDefinition bd = new RootBeanDefinition(ConstructorWithNullableArgument.class);
+ bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
+ bf.registerBeanDefinition("annotatedBean", bd);
+ RootBeanDefinition tb = new RootBeanDefinition(SometimesNullFactoryMethods.class);
+ tb.setFactoryMethodName("createTestBean");
+ tb.setScope(BeanDefinition.SCOPE_PROTOTYPE);
+ bf.registerBeanDefinition("testBean", tb);
+
+ SometimesNullFactoryMethods.active = true;
+ ConstructorWithNullableArgument bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
+ assertThat(bean.getTestBean3()).isNotNull();
+
+ SometimesNullFactoryMethods.active = true;
+ bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
+ assertThat(bean.getTestBean3()).isNotNull();
+
+ SometimesNullFactoryMethods.active = false;
+ bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
+ assertThat(bean.getTestBean3()).isNull();
+
+ SometimesNullFactoryMethods.active = false;
+ bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
+ assertThat(bean.getTestBean3()).isNull();
+
+ SometimesNullFactoryMethods.active = true;
+ bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
+ assertThat(bean.getTestBean3()).isNotNull();
+
+ SometimesNullFactoryMethods.active = false;
+ bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
+ assertThat(bean.getTestBean3()).isNull();
+
+ assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"});
+ }
+
@Test
public void testConstructorResourceInjectionWithCollectionAndNullFromFactoryBean() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(
@@ -2441,13 +2621,12 @@ public static class ResourceInjectionBean {
@Autowired(required = false)
private TestBean testBean;
- private TestBean testBean2;
+ TestBean testBean2;
@Autowired
public void setTestBean2(TestBean testBean2) {
- if (this.testBean2 != null) {
- throw new IllegalStateException("Already called");
- }
+ Assert.state(this.testBean != null, "Wrong initialization order");
+ Assert.state(this.testBean2 == null, "Already called");
this.testBean2 = testBean2;
}
@@ -2482,7 +2661,7 @@ public NonPublicResourceInjectionBean() {
@Required
@SuppressWarnings("deprecation")
public void setTestBean2(TestBean testBean2) {
- super.setTestBean2(testBean2);
+ this.testBean2 = testBean2;
}
@Autowired
@@ -2498,6 +2677,7 @@ private void inject(ITestBean testBean4) {
@Autowired
protected void initBeanFactory(BeanFactory beanFactory) {
+ Assert.state(this.baseInjected, "Wrong initialization order");
this.beanFactory = beanFactory;
}
@@ -2804,6 +2984,21 @@ public ITestBean getTestBean3() {
}
+ public static class ConstructorWithNullableArgument {
+
+ protected ITestBean testBean3;
+
+ @Autowired(required = false)
+ public ConstructorWithNullableArgument(@Nullable ITestBean testBean3) {
+ this.testBean3 = testBean3;
+ }
+
+ public ITestBean getTestBean3() {
+ return this.testBean3;
+ }
+ }
+
+
public static class ConstructorsCollectionResourceInjectionBean {
protected ITestBean testBean3;
@@ -3906,9 +4101,7 @@ public static abstract class Foo> {
private RT obj;
protected void setObj(RT obj) {
- if (this.obj != null) {
- throw new IllegalStateException("Already called");
- }
+ Assert.state(this.obj == null, "Already called");
this.obj = obj;
}
}
diff --git a/spring-beans/src/test/resources/org/springframework/beans/factory/FactoryBeanTests-withAutowiring.xml b/spring-beans/src/test/resources/org/springframework/beans/factory/FactoryBeanTests-withAutowiring.xml
index 90ce2158a93a..1d7d5cdedcf5 100644
--- a/spring-beans/src/test/resources/org/springframework/beans/factory/FactoryBeanTests-withAutowiring.xml
+++ b/spring-beans/src/test/resources/org/springframework/beans/factory/FactoryBeanTests-withAutowiring.xml
@@ -4,25 +4,27 @@
-
+
-
-
-
+
+
+
-
+
-
-
-
- yourName
-
-
-
+
+
+
+ yourName
+ betaFactory
+ getGamma
+
+
+
diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/TestBean.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/TestBean.java
index ed54d0d05f4b..ce870f57846e 100644
--- a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/TestBean.java
+++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/TestBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -477,7 +477,7 @@ public boolean equals(Object other) {
@Override
public int hashCode() {
- return this.age;
+ return TestBean.class.hashCode();
}
@Override
diff --git a/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCache.java b/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCache.java
index ef8c3b03e5bd..5192ba1677a5 100644
--- a/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCache.java
+++ b/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCache.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -59,8 +59,8 @@ public CaffeineCache(String name, com.github.benmanes.caffeine.cache.Cache cache,
boolean allowNullValues) {
@@ -86,7 +86,7 @@ public final com.github.benmanes.caffeine.cache.Cache getNativeC
@SuppressWarnings("unchecked")
@Override
@Nullable
- public T get(Object key, final Callable valueLoader) {
+ public T get(Object key, Callable valueLoader) {
return (T) fromStoreValue(this.cache.get(key, new LoadFunction(valueLoader)));
}
@@ -106,7 +106,7 @@ public void put(Object key, @Nullable Object value) {
@Override
@Nullable
- public ValueWrapper putIfAbsent(Object key, @Nullable final Object value) {
+ public ValueWrapper putIfAbsent(Object key, @Nullable Object value) {
PutIfAbsentFunction callable = new PutIfAbsentFunction(value);
Object result = this.cache.get(key, callable);
return (callable.called ? null : toValueWrapper(result));
@@ -140,7 +140,7 @@ private class PutIfAbsentFunction implements Function {
@Nullable
private final Object value;
- private boolean called;
+ boolean called;
public PutIfAbsentFunction(@Nullable Object value) {
this.value = value;
@@ -159,16 +159,17 @@ private class LoadFunction implements Function {
private final Callable> valueLoader;
public LoadFunction(Callable> valueLoader) {
+ Assert.notNull(valueLoader, "Callable must not be null");
this.valueLoader = valueLoader;
}
@Override
- public Object apply(Object o) {
+ public Object apply(Object key) {
try {
return toStoreValue(this.valueLoader.call());
}
catch (Exception ex) {
- throw new ValueRetrievalException(o, this.valueLoader, ex);
+ throw new ValueRetrievalException(key, this.valueLoader, ex);
}
}
}
diff --git a/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCacheManager.java b/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCacheManager.java
index 239a7350cdc5..e80cb747f396 100644
--- a/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCacheManager.java
+++ b/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCacheManager.java
@@ -110,7 +110,7 @@ public void setCacheNames(@Nullable Collection cacheNames) {
* Set the Caffeine to use for building each individual
* {@link CaffeineCache} instance.
* @see #createNativeCaffeineCache
- * @see com.github.benmanes.caffeine.cache.Caffeine#build()
+ * @see Caffeine#build()
*/
public void setCaffeine(Caffeine caffeine) {
Assert.notNull(caffeine, "Caffeine must not be null");
@@ -121,7 +121,7 @@ public void setCaffeine(Caffeine caffeine) {
* Set the {@link CaffeineSpec} to use for building each individual
* {@link CaffeineCache} instance.
* @see #createNativeCaffeineCache
- * @see com.github.benmanes.caffeine.cache.Caffeine#from(CaffeineSpec)
+ * @see Caffeine#from(CaffeineSpec)
*/
public void setCaffeineSpec(CaffeineSpec caffeineSpec) {
doSetCaffeine(Caffeine.from(caffeineSpec));
@@ -132,7 +132,7 @@ public void setCaffeineSpec(CaffeineSpec caffeineSpec) {
* individual {@link CaffeineCache} instance. The given value needs to
* comply with Caffeine's {@link CaffeineSpec} (see its javadoc).
* @see #createNativeCaffeineCache
- * @see com.github.benmanes.caffeine.cache.Caffeine#from(String)
+ * @see Caffeine#from(String)
*/
public void setCacheSpecification(String cacheSpecification) {
doSetCaffeine(Caffeine.from(cacheSpecification));
@@ -149,7 +149,7 @@ private void doSetCaffeine(Caffeine cacheBuilder) {
* Set the Caffeine CacheLoader to use for building each individual
* {@link CaffeineCache} instance, turning it into a LoadingCache.
* @see #createNativeCaffeineCache
- * @see com.github.benmanes.caffeine.cache.Caffeine#build(CacheLoader)
+ * @see Caffeine#build(CacheLoader)
* @see com.github.benmanes.caffeine.cache.LoadingCache
*/
public void setCacheLoader(CacheLoader cacheLoader) {
diff --git a/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java b/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java
index f8c0de21f2d7..4c1589e97d15 100644
--- a/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java
+++ b/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,19 +38,17 @@ public class CaffeineCacheManagerTests {
@Test
public void testDynamicMode() {
CacheManager cm = new CaffeineCacheManager();
+
Cache cache1 = cm.getCache("c1");
- boolean condition2 = cache1 instanceof CaffeineCache;
- assertThat(condition2).isTrue();
+ assertThat(cache1).isInstanceOf(CaffeineCache.class);
Cache cache1again = cm.getCache("c1");
assertThat(cache1).isSameAs(cache1again);
Cache cache2 = cm.getCache("c2");
- boolean condition1 = cache2 instanceof CaffeineCache;
- assertThat(condition1).isTrue();
+ assertThat(cache2).isInstanceOf(CaffeineCache.class);
Cache cache2again = cm.getCache("c2");
assertThat(cache2).isSameAs(cache2again);
Cache cache3 = cm.getCache("c3");
- boolean condition = cache3 instanceof CaffeineCache;
- assertThat(condition).isTrue();
+ assertThat(cache3).isInstanceOf(CaffeineCache.class);
Cache cache3again = cm.getCache("c3");
assertThat(cache3).isSameAs(cache3again);
@@ -62,19 +60,23 @@ public void testDynamicMode() {
assertThat(cache1.get("key3").get()).isNull();
cache1.evict("key3");
assertThat(cache1.get("key3")).isNull();
+ assertThat(cache1.get("key3", () -> "value3")).isEqualTo("value3");
+ assertThat(cache1.get("key3", () -> "value3")).isEqualTo("value3");
+ cache1.evict("key3");
+ assertThat(cache1.get("key3", () -> (String) null)).isNull();
+ assertThat(cache1.get("key3", () -> (String) null)).isNull();
}
@Test
public void testStaticMode() {
CaffeineCacheManager cm = new CaffeineCacheManager("c1", "c2");
+
Cache cache1 = cm.getCache("c1");
- boolean condition3 = cache1 instanceof CaffeineCache;
- assertThat(condition3).isTrue();
+ assertThat(cache1).isInstanceOf(CaffeineCache.class);
Cache cache1again = cm.getCache("c1");
assertThat(cache1).isSameAs(cache1again);
Cache cache2 = cm.getCache("c2");
- boolean condition2 = cache2 instanceof CaffeineCache;
- assertThat(condition2).isTrue();
+ assertThat(cache2).isInstanceOf(CaffeineCache.class);
Cache cache2again = cm.getCache("c2");
assertThat(cache2).isSameAs(cache2again);
Cache cache3 = cm.getCache("c3");
@@ -91,13 +93,11 @@ public void testStaticMode() {
cm.setAllowNullValues(false);
Cache cache1x = cm.getCache("c1");
- boolean condition1 = cache1x instanceof CaffeineCache;
- assertThat(condition1).isTrue();
- assertThat(cache1x != cache1).isTrue();
+ assertThat(cache1x).isInstanceOf(CaffeineCache.class);
+ assertThat(cache1x).isNotSameAs(cache1);
Cache cache2x = cm.getCache("c2");
- boolean condition = cache2x instanceof CaffeineCache;
- assertThat(condition).isTrue();
- assertThat(cache2x != cache2).isTrue();
+ assertThat(cache2x).isInstanceOf(CaffeineCache.class);
+ assertThat(cache2x).isNotSameAs(cache2);
Cache cache3x = cm.getCache("c3");
assertThat(cache3x).isNull();
@@ -190,7 +190,7 @@ public void cacheLoaderUseLoadingCache() {
assertThat(value.get()).isEqualTo("pong");
assertThatIllegalArgumentException().isThrownBy(() -> assertThat(cache1.get("foo")).isNull())
- .withMessageContaining("I only know ping");
+ .withMessageContaining("I only know ping");
}
@Test
diff --git a/spring-context-support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java b/spring-context-support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java
index 9d461d2e400f..597633475112 100644
--- a/spring-context-support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java
+++ b/spring-context-support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -114,7 +114,7 @@ void schedulerWithTaskExecutor() throws Exception {
trigger.setName("myTrigger");
trigger.setJobDetail(jobDetail);
trigger.setStartDelay(1);
- trigger.setRepeatInterval(500);
+ trigger.setRepeatInterval(100);
trigger.setRepeatCount(1);
trigger.afterPropertiesSet();
@@ -126,14 +126,14 @@ void schedulerWithTaskExecutor() throws Exception {
bean.start();
Thread.sleep(500);
- assertThat(DummyJob.count > 0).as("DummyJob should have been executed at least once.").isTrue();
+ assertThat(DummyJob.count).as("DummyJob should have been executed at least once.").isGreaterThan(0);
assertThat(taskExecutor.count).isEqualTo(DummyJob.count);
bean.destroy();
}
@Test
- @SuppressWarnings({ "unchecked", "rawtypes" })
+ @SuppressWarnings({"unchecked", "rawtypes"})
void jobDetailWithRunnableInsteadOfJob() {
JobDetailImpl jobDetail = new JobDetailImpl();
assertThatIllegalArgumentException().isThrownBy(() ->
@@ -156,7 +156,7 @@ void schedulerWithQuartzJobBean() throws Exception {
trigger.setName("myTrigger");
trigger.setJobDetail(jobDetail);
trigger.setStartDelay(1);
- trigger.setRepeatInterval(500);
+ trigger.setRepeatInterval(100);
trigger.setRepeatCount(1);
trigger.afterPropertiesSet();
@@ -168,7 +168,7 @@ void schedulerWithQuartzJobBean() throws Exception {
Thread.sleep(500);
assertThat(DummyJobBean.param).isEqualTo(10);
- assertThat(DummyJobBean.count > 0).isTrue();
+ assertThat(DummyJobBean.count).isGreaterThan(0);
bean.destroy();
}
@@ -190,7 +190,7 @@ void schedulerWithSpringBeanJobFactory() throws Exception {
trigger.setName("myTrigger");
trigger.setJobDetail(jobDetail);
trigger.setStartDelay(1);
- trigger.setRepeatInterval(500);
+ trigger.setRepeatInterval(100);
trigger.setRepeatCount(1);
trigger.afterPropertiesSet();
@@ -203,7 +203,7 @@ void schedulerWithSpringBeanJobFactory() throws Exception {
Thread.sleep(500);
assertThat(DummyJob.param).isEqualTo(10);
- assertThat(DummyJob.count > 0).as("DummyJob should have been executed at least once.").isTrue();
+ assertThat(DummyJob.count).as("DummyJob should have been executed at least once.").isGreaterThan(0);
bean.destroy();
}
@@ -225,7 +225,7 @@ void schedulerWithSpringBeanJobFactoryAndParamMismatchNotIgnored() throws Except
trigger.setName("myTrigger");
trigger.setJobDetail(jobDetail);
trigger.setStartDelay(1);
- trigger.setRepeatInterval(500);
+ trigger.setRepeatInterval(100);
trigger.setRepeatCount(1);
trigger.afterPropertiesSet();
@@ -239,7 +239,7 @@ void schedulerWithSpringBeanJobFactoryAndParamMismatchNotIgnored() throws Except
Thread.sleep(500);
assertThat(DummyJob.param).isEqualTo(0);
- assertThat(DummyJob.count == 0).isTrue();
+ assertThat(DummyJob.count).isEqualTo(0);
bean.destroy();
}
@@ -260,7 +260,7 @@ void schedulerWithSpringBeanJobFactoryAndQuartzJobBean() throws Exception {
trigger.setName("myTrigger");
trigger.setJobDetail(jobDetail);
trigger.setStartDelay(1);
- trigger.setRepeatInterval(500);
+ trigger.setRepeatInterval(100);
trigger.setRepeatCount(1);
trigger.afterPropertiesSet();
@@ -273,7 +273,7 @@ void schedulerWithSpringBeanJobFactoryAndQuartzJobBean() throws Exception {
Thread.sleep(500);
assertThat(DummyJobBean.param).isEqualTo(10);
- assertThat(DummyJobBean.count > 0).isTrue();
+ assertThat(DummyJobBean.count).isGreaterThan(0);
bean.destroy();
}
@@ -292,7 +292,7 @@ void schedulerWithSpringBeanJobFactoryAndJobSchedulingData() throws Exception {
Thread.sleep(500);
assertThat(DummyJob.param).isEqualTo(10);
- assertThat(DummyJob.count > 0).as("DummyJob should have been executed at least once.").isTrue();
+ assertThat(DummyJob.count).as("DummyJob should have been executed at least once.").isGreaterThan(0);
bean.destroy();
}
diff --git a/spring-context/src/main/java/org/springframework/cache/Cache.java b/spring-context/src/main/java/org/springframework/cache/Cache.java
index 8a3b904f4904..648ff88e3957 100644
--- a/spring-context/src/main/java/org/springframework/cache/Cache.java
+++ b/spring-context/src/main/java/org/springframework/cache/Cache.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,14 +23,20 @@
/**
* Interface that defines common cache operations.
*
- * Note: Due to the generic use of caching, it is recommended that
- * implementations allow storage of {@code null} values (for example to
- * cache methods that return {@code null}).
+ * Serves as an SPI for Spring's annotation-based caching model
+ * ({@link org.springframework.cache.annotation.Cacheable} and co)
+ * as well as an API for direct usage in applications.
+ *
+ *
Note: Due to the generic use of caching, it is recommended
+ * that implementations allow storage of {@code null} values
+ * (for example to cache methods that return {@code null}).
*
* @author Costin Leau
* @author Juergen Hoeller
* @author Stephane Nicoll
* @since 3.1
+ * @see CacheManager
+ * @see org.springframework.cache.annotation.Cacheable
*/
public interface Cache {
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java b/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java
index 6d626e783230..13e097ca2a04 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -175,9 +175,9 @@
*
Only one cache may be specified
* No other cache-related operation can be combined
*
- * This is effectively a hint and the actual cache provider that you are
- * using may not support it in a synchronized fashion. Check your provider
- * documentation for more details on the actual semantics.
+ * This is effectively a hint and the chosen cache provider might not actually
+ * support it in a synchronized fashion. Check your provider documentation for
+ * more details on the actual semantics.
* @since 4.3
* @see org.springframework.cache.Cache#get(Object, Callable)
*/
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheInvoker.java b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheInvoker.java
index 4710f8e1c30d..d5c71acd8a06 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheInvoker.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheInvoker.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -82,12 +82,12 @@ protected Cache.ValueWrapper doGet(Cache cache, Object key) {
* Execute {@link Cache#put(Object, Object)} on the specified {@link Cache}
* and invoke the error handler if an exception occurs.
*/
- protected void doPut(Cache cache, Object key, @Nullable Object result) {
+ protected void doPut(Cache cache, Object key, @Nullable Object value) {
try {
- cache.put(key, result);
+ cache.put(key, value);
}
catch (RuntimeException ex) {
- getErrorHandler().handleCachePutError(ex, cache, key, result);
+ getErrorHandler().handleCachePutError(ex, cache, key, value);
}
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java
index c528a83206bd..077850a2add9 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -214,7 +214,7 @@ public void afterPropertiesSet() {
@Override
public void afterSingletonsInstantiated() {
if (getCacheResolver() == null) {
- // Lazily initialize cache resolver via default cache manager...
+ // Lazily initialize cache resolver via default cache manager
Assert.state(this.beanFactory != null, "CacheResolver or BeanFactory must be set on cache aspect");
try {
setCacheManager(this.beanFactory.getBean(CacheManager.class));
@@ -307,22 +307,22 @@ else if (StringUtils.hasText(operation.getCacheManager())) {
}
/**
- * Return a bean with the specified name and type. Used to resolve services that
- * are referenced by name in a {@link CacheOperation}.
- * @param beanName the name of the bean, as defined by the operation
- * @param expectedType type for the bean
- * @return the bean matching that name
+ * Retrieve a bean with the specified name and type.
+ * Used to resolve services that are referenced by name in a {@link CacheOperation}.
+ * @param name the name of the bean, as defined by the cache operation
+ * @param serviceType the type expected by the operation's service reference
+ * @return the bean matching the expected type, qualified by the given name
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException if such bean does not exist
* @see CacheOperation#getKeyGenerator()
* @see CacheOperation#getCacheManager()
* @see CacheOperation#getCacheResolver()
*/
- protected T getBean(String beanName, Class expectedType) {
+ protected T getBean(String name, Class serviceType) {
if (this.beanFactory == null) {
throw new IllegalStateException(
- "BeanFactory must be set on cache aspect for " + expectedType.getSimpleName() + " retrieval");
+ "BeanFactory must be set on cache aspect for " + serviceType.getSimpleName() + " retrieval");
}
- return BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.beanFactory, expectedType, beanName);
+ return BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.beanFactory, serviceType, name);
}
/**
@@ -388,21 +388,20 @@ private Object execute(final CacheOperationInvoker invoker, Method method, Cache
}
}
else {
- // No caching required, only call the underlying method
+ // No caching required, just call the underlying method
return invokeOperation(invoker);
}
}
-
// Process any early evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);
- // Check if we have a cached item matching the conditions
+ // Check if we have a cached value matching the conditions
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
- // Collect puts from any @Cacheable miss, if no cached item is found
- List cachePutRequests = new ArrayList<>();
+ // Collect puts from any @Cacheable miss, if no cached value is found
+ List cachePutRequests = new ArrayList<>(1);
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
@@ -469,7 +468,7 @@ private Object unwrapReturnValue(@Nullable Object returnValue) {
private boolean hasCachePut(CacheOperationContexts contexts) {
// Evaluate the conditions *without* the result object because we don't have it yet...
Collection cachePutContexts = contexts.get(CachePutOperation.class);
- Collection excluded = new ArrayList<>();
+ Collection excluded = new ArrayList<>(1);
for (CacheOperationContext context : cachePutContexts) {
try {
if (!context.isConditionPassing(CacheOperationExpressionEvaluator.RESULT_UNAVAILABLE)) {
@@ -522,9 +521,9 @@ private void logInvalidating(CacheOperationContext context, CacheEvictOperation
}
/**
- * Find a cached item only for {@link CacheableOperation} that passes the condition.
+ * Find a cached value only for {@link CacheableOperation} that passes the condition.
* @param contexts the cacheable operations
- * @return a {@link Cache.ValueWrapper} holding the cached item,
+ * @return a {@link Cache.ValueWrapper} holding the cached value,
* or {@code null} if none is found
*/
@Nullable
@@ -549,9 +548,9 @@ private Cache.ValueWrapper findCachedItem(Collection cont
/**
* Collect the {@link CachePutRequest} for all {@link CacheOperation} using
- * the specified result item.
+ * the specified result value.
* @param contexts the contexts to handle
- * @param result the result item (never {@code null})
+ * @param result the result value (never {@code null})
* @param putRequests the collection to update
*/
private void collectPutRequests(Collection contexts,
@@ -641,21 +640,22 @@ private boolean determineSyncFlag(Method method) {
if (syncEnabled) {
if (this.contexts.size() > 1) {
throw new IllegalStateException(
- "@Cacheable(sync=true) cannot be combined with other cache operations on '" + method + "'");
+ "A sync=true operation cannot be combined with other cache operations on '" + method + "'");
}
if (cacheOperationContexts.size() > 1) {
throw new IllegalStateException(
- "Only one @Cacheable(sync=true) entry is allowed on '" + method + "'");
+ "Only one sync=true operation is allowed on '" + method + "'");
}
CacheOperationContext cacheOperationContext = cacheOperationContexts.iterator().next();
- CacheableOperation operation = (CacheableOperation) cacheOperationContext.getOperation();
+ CacheOperation operation = cacheOperationContext.getOperation();
if (cacheOperationContext.getCaches().size() > 1) {
throw new IllegalStateException(
- "@Cacheable(sync=true) only allows a single cache on '" + operation + "'");
+ "A sync=true operation is restricted to a single cache on '" + operation + "'");
}
- if (StringUtils.hasText(operation.getUnless())) {
+ if (operation instanceof CacheableOperation &&
+ StringUtils.hasText(((CacheableOperation) operation).getUnless())) {
throw new IllegalStateException(
- "@Cacheable(sync=true) does not support unless attribute on '" + operation + "'");
+ "A sync=true operation does not support the unless attribute on '" + operation + "'");
}
return true;
}
@@ -722,7 +722,7 @@ public CacheOperationContext(CacheOperationMetadata metadata, Object[] args, Obj
this.args = extractArgs(metadata.method, args);
this.target = target;
this.caches = CacheAspectSupport.this.getCaches(this, metadata.cacheResolver);
- this.cacheNames = createCacheNames(this.caches);
+ this.cacheNames = prepareCacheNames(this.caches);
}
@Override
@@ -810,8 +810,8 @@ protected Collection getCacheNames() {
return this.cacheNames;
}
- private Collection createCacheNames(Collection extends Cache> caches) {
- Collection names = new ArrayList<>();
+ private Collection prepareCacheNames(Collection extends Cache> caches) {
+ Collection names = new ArrayList<>(caches.size());
for (Cache cache : caches) {
names.add(cache.getName());
}
@@ -885,13 +885,13 @@ public int compareTo(CacheOperationCacheKey other) {
}
}
+
/**
* Internal holder class for recording that a cache method was invoked.
*/
private static class InvocationAwareResult {
boolean invoked;
-
}
}
diff --git a/spring-context/src/main/java/org/springframework/cache/support/SimpleCacheManager.java b/spring-context/src/main/java/org/springframework/cache/support/SimpleCacheManager.java
index c130f8f2698c..08500e04606a 100644
--- a/spring-context/src/main/java/org/springframework/cache/support/SimpleCacheManager.java
+++ b/spring-context/src/main/java/org/springframework/cache/support/SimpleCacheManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author 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,14 +24,16 @@
/**
* Simple cache manager working against a given collection of caches.
* Useful for testing or simple caching declarations.
- *
- * When using this implementation directly, i.e. not via a regular
+ *
+ *
When using this implementation directly, i.e. not via a regular
* bean registration, {@link #initializeCaches()} should be invoked
* to initialize its internal state once the
* {@linkplain #setCaches(Collection) caches have been provided}.
*
* @author Costin Leau
* @since 3.1
+ * @see NoOpCache
+ * @see org.springframework.cache.concurrent.ConcurrentMapCache
*/
public class SimpleCacheManager extends AbstractCacheManager {
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java
index 86ea5feb7335..6ca78078cb18 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -65,7 +65,7 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex
* through {@link #register} calls and then manually {@linkplain #refresh refreshed}.
*/
public AnnotationConfigApplicationContext() {
- StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
+ StartupStep createAnnotatedBeanDefReader = getApplicationStartup().start("spring.context.annotated-bean-reader.create");
this.reader = new AnnotatedBeanDefinitionReader(this);
createAnnotatedBeanDefReader.end();
this.scanner = new ClassPathBeanDefinitionScanner(this);
@@ -163,7 +163,7 @@ public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver
@Override
public void register(Class>... componentClasses) {
Assert.notEmpty(componentClasses, "At least one component class must be specified");
- StartupStep registerComponentClass = this.getApplicationStartup().start("spring.context.component-classes.register")
+ StartupStep registerComponentClass = getApplicationStartup().start("spring.context.component-classes.register")
.tag("classes", () -> Arrays.toString(componentClasses));
this.reader.register(componentClasses);
registerComponentClass.end();
@@ -180,7 +180,7 @@ public void register(Class>... componentClasses) {
@Override
public void scan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
- StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan")
+ StartupStep scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")
.tag("packages", () -> Arrays.toString(basePackages));
this.scanner.scan(basePackages);
scanPackages.end();
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/BeanMethod.java b/spring-context/src/main/java/org/springframework/context/annotation/BeanMethod.java
index 6d3459fb08c7..abe73dacc60f 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/BeanMethod.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/BeanMethod.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,6 +39,7 @@ final class BeanMethod extends ConfigurationMethod {
super(metadata, configurationClass);
}
+
@Override
public void validate(ProblemReporter problemReporter) {
if (getMetadata().isStatic()) {
@@ -55,9 +56,9 @@ public void validate(ProblemReporter problemReporter) {
}
@Override
- public boolean equals(@Nullable Object obj) {
- return ((this == obj) || ((obj instanceof BeanMethod) &&
- this.metadata.equals(((BeanMethod) obj).metadata)));
+ public boolean equals(@Nullable Object other) {
+ return (this == other || (other instanceof BeanMethod &&
+ this.metadata.equals(((BeanMethod) other).metadata)));
}
@Override
@@ -70,6 +71,7 @@ public String toString() {
return "BeanMethod: " + this.metadata;
}
+
private class NonOverridableMethodError extends Problem {
NonOverridableMethodError() {
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java
index 65cbb9bdb9f2..7f41cd5962ce 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -329,8 +329,8 @@ protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, Bea
* @return {@code true} if the bean can be registered as-is;
* {@code false} if it should be skipped because there is an
* existing, compatible bean definition for the specified name
- * @throws ConflictingBeanDefinitionException if an existing, incompatible
- * bean definition has been found for the specified name
+ * @throws IllegalStateException if an existing, incompatible bean definition
+ * has been found for the specified name
*/
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
if (!this.registry.containsBeanDefinition(beanName)) {
@@ -354,16 +354,16 @@ protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition)
* the given existing bean definition.
*
The default implementation considers them as compatible when the existing
* bean definition comes from the same source or from a non-scanning source.
- * @param newDefinition the new bean definition, originated from scanning
- * @param existingDefinition the existing bean definition, potentially an
+ * @param newDef the new bean definition, originated from scanning
+ * @param existingDef the existing bean definition, potentially an
* explicitly defined one or a previously generated one from scanning
* @return whether the definitions are considered as compatible, with the
* new definition to be skipped in favor of the existing definition
*/
- protected boolean isCompatible(BeanDefinition newDefinition, BeanDefinition existingDefinition) {
- return (!(existingDefinition instanceof ScannedGenericBeanDefinition) || // explicitly registered overriding bean
- (newDefinition.getSource() != null && newDefinition.getSource().equals(existingDefinition.getSource())) || // scanned same file twice
- newDefinition.equals(existingDefinition)); // scanned equivalent class twice
+ protected boolean isCompatible(BeanDefinition newDef, BeanDefinition existingDef) {
+ return (!(existingDef instanceof ScannedGenericBeanDefinition) || // explicitly registered overriding bean
+ (newDef.getSource() != null && newDef.getSource().equals(existingDef.getSource())) || // scanned same file twice
+ newDef.equals(existingDef)); // scanned equivalent class twice
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java
index 427e2bec83b2..fe050329e727 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -424,6 +424,7 @@ public ConfigurationClassBeanDefinition(
public ConfigurationClassBeanDefinition(RootBeanDefinition original,
ConfigurationClass configClass, MethodMetadata beanMethodMetadata, String derivedBeanName) {
+
super(original);
this.annotationMetadata = configClass.getMetadata();
this.factoryMethodMetadata = beanMethodMetadata;
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java
index fe58c8f2d648..633780ec4a70 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -409,11 +409,14 @@ private Set retrieveBeanMethodMetadata(SourceClass sourceClass)
this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();
Set asmMethods = asm.getAnnotatedMethods(Bean.class.getName());
if (asmMethods.size() >= beanMethods.size()) {
+ Set candidateMethods = new LinkedHashSet<>(beanMethods);
Set selectedMethods = new LinkedHashSet<>(asmMethods.size());
for (MethodMetadata asmMethod : asmMethods) {
- for (MethodMetadata beanMethod : beanMethods) {
+ for (Iterator it = candidateMethods.iterator(); it.hasNext();) {
+ MethodMetadata beanMethod = it.next();
if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
selectedMethods.add(beanMethod);
+ it.remove();
break;
}
}
diff --git a/spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java b/spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java
index d1f9b8ca330a..2de53ce29021 100644
--- a/spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java
+++ b/spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -79,10 +79,11 @@ public SimpleApplicationEventMulticaster(BeanFactory beanFactory) {
* to invoke each listener with.
* Default is equivalent to {@link org.springframework.core.task.SyncTaskExecutor},
* executing all listeners synchronously in the calling thread.
- *
Consider specifying an asynchronous task executor here to not block the
- * caller until all listeners have been executed. However, note that asynchronous
- * execution will not participate in the caller's thread context (class loader,
- * transaction association) unless the TaskExecutor explicitly supports this.
+ *
Consider specifying an asynchronous task executor here to not block the caller
+ * until all listeners have been executed. However, note that asynchronous execution
+ * will not participate in the caller's thread context (class loader, transaction context)
+ * unless the TaskExecutor explicitly supports this.
+ * @since 2.0
* @see org.springframework.core.task.SyncTaskExecutor
* @see org.springframework.core.task.SimpleAsyncTaskExecutor
*/
@@ -92,6 +93,7 @@ public void setTaskExecutor(@Nullable Executor taskExecutor) {
/**
* Return the current task executor for this multicaster.
+ * @since 2.0
*/
@Nullable
protected Executor getTaskExecutor() {
diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java
index 5d69a6533504..a92455608fc5 100644
--- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java
+++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java
@@ -1083,6 +1083,9 @@ protected void doClose() {
// Let subclasses do some final clean-up if they wish...
onClose();
+ // Reset common introspection caches to avoid class reference leaks.
+ resetCommonCaches();
+
// Reset local application listeners to pre-refresh state.
if (this.earlyApplicationListeners != null) {
this.applicationListeners.clear();
diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractXmlApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractXmlApplicationContext.java
index 85cb2250b59e..c21002abd2cc 100644
--- a/spring-context/src/main/java/org/springframework/context/support/AbstractXmlApplicationContext.java
+++ b/spring-context/src/main/java/org/springframework/context/support/AbstractXmlApplicationContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.beans.factory.xml.BeanDefinitionDocumentReader;
import org.springframework.beans.factory.xml.ResourceEntityResolver;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
@@ -84,7 +85,7 @@ protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throw
// Configure the bean definition reader with this context's
// resource loading environment.
- beanDefinitionReader.setEnvironment(this.getEnvironment());
+ beanDefinitionReader.setEnvironment(getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
@@ -95,12 +96,13 @@ protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throw
}
/**
- * Initialize the bean definition reader used for loading the bean
- * definitions of this context. Default implementation is empty.
+ * Initialize the bean definition reader used for loading the bean definitions
+ * of this context. The default implementation sets the validating flag.
*
Can be overridden in subclasses, e.g. for turning off XML validation
- * or using a different XmlBeanDefinitionParser implementation.
+ * or using a different {@link BeanDefinitionDocumentReader} implementation.
* @param reader the bean definition reader used by this context
- * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader#setDocumentReaderClass
+ * @see XmlBeanDefinitionReader#setValidating
+ * @see XmlBeanDefinitionReader#setDocumentReaderClass
*/
protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
reader.setValidating(this.validating);
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
index 983d341e7823..319165d2bec0 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
@@ -28,8 +28,8 @@
/**
* Annotation that marks a method to be scheduled. Exactly one of the
- * {@link #cron}, {@link #fixedDelay}, or {@link #fixedRate} attributes must be
- * specified.
+ * {@link #cron}, {@link #fixedDelay}, or {@link #fixedRate} attributes
+ * must be specified.
*
*
The annotated method must expect no arguments. It will typically have
* a {@code void} return type; if not, the returned value will be ignored
@@ -40,6 +40,12 @@
* done manually or, more conveniently, through the {@code }
* XML element or {@link EnableScheduling @EnableScheduling} annotation.
*
+ *
This annotation can be used as a {@linkplain Repeatable repeatable}
+ * annotation. If several scheduled declarations are found on the same method,
+ * each of them will be processed independently, with a separate trigger firing
+ * for each of them. As a consequence, such co-located schedules may overlap
+ * and execute multiple times in parallel or in immediate succession.
+ *
*
This annotation may be used as a meta-annotation to create custom
* composed annotations with attribute overrides.
*
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java
index f0aae203468a..db1372b70cba 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -160,7 +160,7 @@ public ScheduledAnnotationBeanPostProcessor() {
* @since 5.1
*/
public ScheduledAnnotationBeanPostProcessor(ScheduledTaskRegistrar registrar) {
- Assert.notNull(registrar, "ScheduledTaskRegistrar is required");
+ Assert.notNull(registrar, "ScheduledTaskRegistrar must not be null");
this.registrar = registrar;
}
@@ -580,7 +580,7 @@ public void postProcessBeforeDestruction(Object bean, String beanName) {
}
if (tasks != null) {
for (ScheduledTask task : tasks) {
- task.cancel();
+ task.cancel(false);
}
}
}
@@ -598,7 +598,7 @@ public void destroy() {
Collection> allTasks = this.scheduledTasks.values();
for (Set tasks : allTasks) {
for (ScheduledTask task : tasks) {
- task.cancel();
+ task.cancel(false);
}
}
this.scheduledTasks.clear();
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/SchedulingConfigurer.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/SchedulingConfigurer.java
index 26f0076077f7..7ad039fb8dd7 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/SchedulingConfigurer.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/SchedulingConfigurer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,10 +40,10 @@
public interface SchedulingConfigurer {
/**
- * Callback allowing a {@link org.springframework.scheduling.TaskScheduler
- * TaskScheduler} and specific {@link org.springframework.scheduling.config.Task Task}
- * instances to be registered against the given the {@link ScheduledTaskRegistrar}.
- * @param taskRegistrar the registrar to be configured.
+ * Callback allowing a {@link org.springframework.scheduling.TaskScheduler}
+ * and specific {@link org.springframework.scheduling.config.Task} instances
+ * to be registered against the given the {@link ScheduledTaskRegistrar}.
+ * @param taskRegistrar the registrar to be configured
*/
void configureTasks(ScheduledTaskRegistrar taskRegistrar);
diff --git a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java
index 3b9cc27aa16c..9f141a33cc51 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,8 +41,8 @@
* Helper bean for registering tasks with a {@link TaskScheduler}, typically using cron
* expressions.
*
- * As of Spring 3.1, {@code ScheduledTaskRegistrar} has a more prominent user-facing
- * role when used in conjunction with the {@link
+ *
{@code ScheduledTaskRegistrar} has a more prominent user-facing role when used in
+ * conjunction with the {@link
* org.springframework.scheduling.annotation.EnableAsync @EnableAsync} annotation and its
* {@link org.springframework.scheduling.annotation.SchedulingConfigurer
* SchedulingConfigurer} callback interface.
@@ -552,7 +552,7 @@ public Set getScheduledTasks() {
@Override
public void destroy() {
for (ScheduledTask task : this.scheduledTasks) {
- task.cancel();
+ task.cancel(false);
}
if (this.localExecutor != null) {
this.localExecutor.shutdownNow();
diff --git a/spring-context/src/main/java/org/springframework/validation/AbstractBindingResult.java b/spring-context/src/main/java/org/springframework/validation/AbstractBindingResult.java
index b8857b15b50b..18278e009d90 100644
--- a/spring-context/src/main/java/org/springframework/validation/AbstractBindingResult.java
+++ b/spring-context/src/main/java/org/springframework/validation/AbstractBindingResult.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -102,8 +102,8 @@ public void reject(String errorCode, @Nullable Object[] errorArgs, @Nullable Str
}
@Override
- public void rejectValue(@Nullable String field, String errorCode, @Nullable Object[] errorArgs,
- @Nullable String defaultMessage) {
+ public void rejectValue(@Nullable String field, String errorCode,
+ @Nullable Object[] errorArgs, @Nullable String defaultMessage) {
if (!StringUtils.hasLength(getNestedPath()) && !StringUtils.hasLength(field)) {
// We're at the top of the nested object hierarchy,
diff --git a/spring-context/src/main/java/org/springframework/validation/AbstractErrors.java b/spring-context/src/main/java/org/springframework/validation/AbstractErrors.java
index 9556dc3a286e..b9e5617a53c6 100644
--- a/spring-context/src/main/java/org/springframework/validation/AbstractErrors.java
+++ b/spring-context/src/main/java/org/springframework/validation/AbstractErrors.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author 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,13 +28,14 @@
import org.springframework.util.StringUtils;
/**
- * Abstract implementation of the {@link Errors} interface. Provides common
- * access to evaluated errors; however, does not define concrete management
+ * Abstract implementation of the {@link Errors} interface.
+ * Provides nested path handling but does not define concrete management
* of {@link ObjectError ObjectErrors} and {@link FieldError FieldErrors}.
*
* @author Juergen Hoeller
* @author Rossen Stoyanchev
* @since 2.5.3
+ * @see AbstractBindingResult
*/
@SuppressWarnings("serial")
public abstract class AbstractErrors implements Errors, Serializable {
@@ -81,8 +82,8 @@ protected void doSetNestedPath(@Nullable String nestedPath) {
nestedPath = "";
}
nestedPath = canonicalFieldName(nestedPath);
- if (nestedPath.length() > 0 && !nestedPath.endsWith(Errors.NESTED_PATH_SEPARATOR)) {
- nestedPath += Errors.NESTED_PATH_SEPARATOR;
+ if (nestedPath.length() > 0 && !nestedPath.endsWith(NESTED_PATH_SEPARATOR)) {
+ nestedPath += NESTED_PATH_SEPARATOR;
}
this.nestedPath = nestedPath;
}
@@ -97,7 +98,7 @@ protected String fixedField(@Nullable String field) {
}
else {
String path = getNestedPath();
- return (path.endsWith(Errors.NESTED_PATH_SEPARATOR) ?
+ return (path.endsWith(NESTED_PATH_SEPARATOR) ?
path.substring(0, path.length() - NESTED_PATH_SEPARATOR.length()) : path);
}
}
@@ -201,9 +202,9 @@ public List getFieldErrors(String field) {
List fieldErrors = getFieldErrors();
List result = new ArrayList<>();
String fixedField = fixedField(field);
- for (FieldError error : fieldErrors) {
- if (isMatchingFieldError(fixedField, error)) {
- result.add(error);
+ for (FieldError fieldError : fieldErrors) {
+ if (isMatchingFieldError(fixedField, fieldError)) {
+ result.add(fieldError);
}
}
return Collections.unmodifiableList(result);
diff --git a/spring-context/src/main/java/org/springframework/validation/BeanPropertyBindingResult.java b/spring-context/src/main/java/org/springframework/validation/BeanPropertyBindingResult.java
index 8558a2619c97..1cc6bb8e3654 100644
--- a/spring-context/src/main/java/org/springframework/validation/BeanPropertyBindingResult.java
+++ b/spring-context/src/main/java/org/springframework/validation/BeanPropertyBindingResult.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -55,7 +55,7 @@ public class BeanPropertyBindingResult extends AbstractPropertyBindingResult imp
/**
- * Creates a new instance of the {@link BeanPropertyBindingResult} class.
+ * Create a new {@code BeanPropertyBindingResult} for the given target.
* @param target the target bean to bind onto
* @param objectName the name of the target object
*/
@@ -64,7 +64,7 @@ public BeanPropertyBindingResult(@Nullable Object target, String objectName) {
}
/**
- * Creates a new instance of the {@link BeanPropertyBindingResult} class.
+ * Create a new {@code BeanPropertyBindingResult} for the given target.
* @param target the target bean to bind onto
* @param objectName the name of the target object
* @param autoGrowNestedPaths whether to "auto-grow" a nested path that contains a null value
diff --git a/spring-context/src/main/java/org/springframework/validation/BindException.java b/spring-context/src/main/java/org/springframework/validation/BindException.java
index afc0c5c2ae71..b84c81081a87 100644
--- a/spring-context/src/main/java/org/springframework/validation/BindException.java
+++ b/spring-context/src/main/java/org/springframework/validation/BindException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -128,7 +128,9 @@ public void rejectValue(@Nullable String field, String errorCode, String default
}
@Override
- public void rejectValue(@Nullable String field, String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage) {
+ public void rejectValue(@Nullable String field, String errorCode,
+ @Nullable Object[] errorArgs, @Nullable String defaultMessage) {
+
this.bindingResult.rejectValue(field, errorCode, errorArgs, defaultMessage);
}
diff --git a/spring-context/src/main/java/org/springframework/validation/DirectFieldBindingResult.java b/spring-context/src/main/java/org/springframework/validation/DirectFieldBindingResult.java
index 5ad401de5bec..f01232ca1f6f 100644
--- a/spring-context/src/main/java/org/springframework/validation/DirectFieldBindingResult.java
+++ b/spring-context/src/main/java/org/springframework/validation/DirectFieldBindingResult.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,7 +46,7 @@ public class DirectFieldBindingResult extends AbstractPropertyBindingResult {
/**
- * Create a new DirectFieldBindingResult instance.
+ * Create a new {@code DirectFieldBindingResult} for the given target.
* @param target the target object to bind onto
* @param objectName the name of the target object
*/
@@ -55,7 +55,7 @@ public DirectFieldBindingResult(@Nullable Object target, String objectName) {
}
/**
- * Create a new DirectFieldBindingResult instance.
+ * Create a new {@code DirectFieldBindingResult} for the given target.
* @param target the target object to bind onto
* @param objectName the name of the target object
* @param autoGrowNestedPaths whether to "auto-grow" a nested path that contains a null value
diff --git a/spring-context/src/main/java/org/springframework/validation/Errors.java b/spring-context/src/main/java/org/springframework/validation/Errors.java
index 45ae5d7b57af..18a7bc1910af 100644
--- a/spring-context/src/main/java/org/springframework/validation/Errors.java
+++ b/spring-context/src/main/java/org/springframework/validation/Errors.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,24 +22,24 @@
import org.springframework.lang.Nullable;
/**
- * Stores and exposes information about data-binding and validation
- * errors for a specific object.
+ * Stores and exposes information about data-binding and validation errors
+ * for a specific object.
*
- * Field names can be properties of the target object (e.g. "name"
- * when binding to a customer object), or nested fields in case of
- * subobjects (e.g. "address.street"). Supports subtree navigation
- * via {@link #setNestedPath(String)}: for example, an
- * {@code AddressValidator} validates "address", not being aware
- * that this is a subobject of customer.
+ *
Field names are typically properties of the target object (e.g. "name"
+ * when binding to a customer object). Implementations may also support nested
+ * fields in case of nested objects (e.g. "address.street"), in conjunction
+ * with subtree navigation via {@link #setNestedPath}: for example, an
+ * {@code AddressValidator} may validate "address", not being aware that this
+ * is a nested object of a top-level customer object.
*
*
Note: {@code Errors} objects are single-threaded.
*
* @author Rod Johnson
* @author Juergen Hoeller
- * @see #setNestedPath
- * @see BindException
- * @see DataBinder
+ * @see Validator
* @see ValidationUtils
+ * @see BindException
+ * @see BindingResult
*/
public interface Errors {
@@ -66,6 +66,7 @@ public interface Errors {
* @param nestedPath nested path within this object,
* e.g. "address" (defaults to "", {@code null} is also acceptable).
* Can end with a dot: both "address" and "address." are valid.
+ * @see #getNestedPath()
*/
void setNestedPath(String nestedPath);
@@ -73,6 +74,7 @@ public interface Errors {
* Return the current nested path of this {@link Errors} object.
*
Returns a nested path with a dot, i.e. "address.", for easy
* building of concatenated paths. Default is an empty String.
+ * @see #setNestedPath(String)
*/
String getNestedPath();
@@ -86,14 +88,14 @@ public interface Errors {
*
For example: current path "spouse.", pushNestedPath("child") →
* result path "spouse.child."; popNestedPath() → "spouse." again.
* @param subPath the sub path to push onto the nested path stack
- * @see #popNestedPath
+ * @see #popNestedPath()
*/
void pushNestedPath(String subPath);
/**
* Pop the former nested path from the nested path stack.
* @throws IllegalStateException if there is no former nested path on the stack
- * @see #pushNestedPath
+ * @see #pushNestedPath(String)
*/
void popNestedPath() throws IllegalStateException;
@@ -101,6 +103,7 @@ public interface Errors {
* Register a global error for the entire target object,
* using the given error description.
* @param errorCode error code, interpretable as a message key
+ * @see #reject(String, Object[], String)
*/
void reject(String errorCode);
@@ -109,6 +112,7 @@ public interface Errors {
* using the given error description.
* @param errorCode error code, interpretable as a message key
* @param defaultMessage fallback default message
+ * @see #reject(String, Object[], String)
*/
void reject(String errorCode, String defaultMessage);
@@ -119,6 +123,7 @@ public interface Errors {
* @param errorArgs error arguments, for argument binding via MessageFormat
* (can be {@code null})
* @param defaultMessage fallback default message
+ * @see #rejectValue(String, String, Object[], String)
*/
void reject(String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage);
@@ -132,7 +137,7 @@ public interface Errors {
* global error if the current object is the top object.
* @param field the field name (may be {@code null} or empty String)
* @param errorCode error code, interpretable as a message key
- * @see #getNestedPath()
+ * @see #rejectValue(String, String, Object[], String)
*/
void rejectValue(@Nullable String field, String errorCode);
@@ -147,7 +152,7 @@ public interface Errors {
* @param field the field name (may be {@code null} or empty String)
* @param errorCode error code, interpretable as a message key
* @param defaultMessage fallback default message
- * @see #getNestedPath()
+ * @see #rejectValue(String, String, Object[], String)
*/
void rejectValue(@Nullable String field, String errorCode, String defaultMessage);
@@ -164,7 +169,7 @@ public interface Errors {
* @param errorArgs error arguments, for argument binding via MessageFormat
* (can be {@code null})
* @param defaultMessage fallback default message
- * @see #getNestedPath()
+ * @see #reject(String, Object[], String)
*/
void rejectValue(@Nullable String field, String errorCode,
@Nullable Object[] errorArgs, @Nullable String defaultMessage);
@@ -179,35 +184,40 @@ void rejectValue(@Nullable String field, String errorCode,
* to refer to the same target object, or at least contain compatible errors
* that apply to the target object of this {@code Errors} instance.
* @param errors the {@code Errors} instance to merge in
+ * @see #getAllErrors()
*/
void addAllErrors(Errors errors);
/**
- * Return if there were any errors.
+ * Determine if there were any errors.
+ * @see #hasGlobalErrors()
+ * @see #hasFieldErrors()
*/
boolean hasErrors();
/**
- * Return the total number of errors.
+ * Determine the total number of errors.
+ * @see #getGlobalErrorCount()
+ * @see #getFieldErrorCount()
*/
int getErrorCount();
/**
* Get all errors, both global and field ones.
- * @return a list of {@link ObjectError} instances
+ * @return a list of {@link ObjectError}/{@link FieldError} instances
+ * @see #getGlobalErrors()
+ * @see #getFieldErrors()
*/
List getAllErrors();
/**
- * Are there any global errors?
- * @return {@code true} if there are any global errors
+ * Determine if there were any global errors.
* @see #hasFieldErrors()
*/
boolean hasGlobalErrors();
/**
- * Return the number of global errors.
- * @return the number of global errors
+ * Determine the number of global errors.
* @see #getFieldErrorCount()
*/
int getGlobalErrorCount();
@@ -215,26 +225,26 @@ void rejectValue(@Nullable String field, String errorCode,
/**
* Get all global errors.
* @return a list of {@link ObjectError} instances
+ * @see #getFieldErrors()
*/
List getGlobalErrors();
/**
* Get the first global error, if any.
* @return the global error, or {@code null}
+ * @see #getFieldError()
*/
@Nullable
ObjectError getGlobalError();
/**
- * Are there any field errors?
- * @return {@code true} if there are any errors associated with a field
+ * Determine if there were any errors associated with a field.
* @see #hasGlobalErrors()
*/
boolean hasFieldErrors();
/**
- * Return the number of errors associated with a field.
- * @return the number of errors associated with a field
+ * Determine the number of errors associated with a field.
* @see #getGlobalErrorCount()
*/
int getFieldErrorCount();
@@ -242,36 +252,39 @@ void rejectValue(@Nullable String field, String errorCode,
/**
* Get all errors associated with a field.
* @return a List of {@link FieldError} instances
+ * @see #getGlobalErrors()
*/
List getFieldErrors();
/**
* Get the first error associated with a field, if any.
* @return the field-specific error, or {@code null}
+ * @see #getGlobalError()
*/
@Nullable
FieldError getFieldError();
/**
- * Are there any errors associated with the given field?
+ * Determine if there were any errors associated with the given field.
* @param field the field name
- * @return {@code true} if there were any errors associated with the given field
+ * @see #hasFieldErrors()
*/
boolean hasFieldErrors(String field);
/**
- * Return the number of errors associated with the given field.
+ * Determine the number of errors associated with the given field.
* @param field the field name
- * @return the number of errors associated with the given field
+ * @see #getFieldErrorCount()
*/
int getFieldErrorCount(String field);
/**
* Get all errors associated with the given field.
- * Implementations should support not only full field names like
- * "name" but also pattern matches like "na*" or "address.*".
+ *
Implementations may support not only full field names like
+ * "address.street" but also pattern matches like "address.*".
* @param field the field name
* @return a List of {@link FieldError} instances
+ * @see #getFieldErrors()
*/
List getFieldErrors(String field);
@@ -279,6 +292,7 @@ void rejectValue(@Nullable String field, String errorCode,
* Get the first error associated with the given field, if any.
* @param field the field name
* @return the field-specific error, or {@code null}
+ * @see #getFieldError()
*/
@Nullable
FieldError getFieldError(String field);
@@ -290,17 +304,19 @@ void rejectValue(@Nullable String field, String errorCode,
* even if there were type mismatches.
* @param field the field name
* @return the current value of the given field
+ * @see #getFieldType(String)
*/
@Nullable
Object getFieldValue(String field);
/**
- * Return the type of a given field.
+ * Determine the type of the given field, as far as possible.
* Implementations should be able to determine the type even
* when the field value is {@code null}, for example from some
* associated descriptor.
* @param field the field name
* @return the type of the field, or {@code null} if not determinable
+ * @see #getFieldValue(String)
*/
@Nullable
Class> getFieldType(String field);
diff --git a/spring-context/src/main/java/org/springframework/validation/Validator.java b/spring-context/src/main/java/org/springframework/validation/Validator.java
index b67b6d5d8b77..2aa282396358 100644
--- a/spring-context/src/main/java/org/springframework/validation/Validator.java
+++ b/spring-context/src/main/java/org/springframework/validation/Validator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -54,14 +54,14 @@
* }
* }
*
- *
See also the Spring reference manual for a fuller discussion of
- * the {@code Validator} interface and its role in an enterprise
- * application.
+ *
See also the Spring reference manual for a fuller discussion of the
+ * {@code Validator} interface and its role in an enterprise application.
*
* @author Rod Johnson
* @see SmartValidator
* @see Errors
* @see ValidationUtils
+ * @see DataBinder#setValidator
*/
public interface Validator {
@@ -81,11 +81,14 @@ public interface Validator {
boolean supports(Class> clazz);
/**
- * Validate the supplied {@code target} object, which must be
- * of a {@link Class} for which the {@link #supports(Class)} method
- * typically has (or would) return {@code true}.
+ * Validate the given {@code target} object which must be of a
+ * {@link Class} for which the {@link #supports(Class)} method
+ * typically has returned (or would return) {@code true}.
*
The supplied {@link Errors errors} instance can be used to report
- * any resulting validation errors.
+ * any resulting validation errors, typically as part of a larger
+ * binding process which this validator is meant to participate in.
+ * Binding errors have typically been pre-registered with the
+ * {@link Errors errors} instance before this invocation already.
* @param target the object that is to be validated
* @param errors contextual state about the validation process
* @see ValidationUtils
diff --git a/spring-context/src/main/java/org/springframework/validation/annotation/ValidationAnnotationUtils.java b/spring-context/src/main/java/org/springframework/validation/annotation/ValidationAnnotationUtils.java
index 196f6fc6c74e..0842b812ba0e 100644
--- a/spring-context/src/main/java/org/springframework/validation/annotation/ValidationAnnotationUtils.java
+++ b/spring-context/src/main/java/org/springframework/validation/annotation/ValidationAnnotationUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author 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,37 +26,46 @@
* Mainly for internal use within the framework.
*
* @author Christoph Dreis
+ * @author Juergen Hoeller
* @since 5.3.7
*/
public abstract class ValidationAnnotationUtils {
private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
+
/**
* Determine any validation hints by the given annotation.
- *
This implementation checks for {@code @javax.validation.Valid},
- * Spring's {@link org.springframework.validation.annotation.Validated},
- * and custom annotations whose name starts with "Valid".
+ *
This implementation checks for Spring's
+ * {@link org.springframework.validation.annotation.Validated},
+ * {@code @javax.validation.Valid}, and custom annotations whose
+ * name starts with "Valid" which may optionally declare validation
+ * hints through the "value" attribute.
* @param ann the annotation (potentially a validation annotation)
* @return the validation hints to apply (possibly an empty array),
* or {@code null} if this annotation does not trigger any validation
*/
@Nullable
public static Object[] determineValidationHints(Annotation ann) {
+ // Direct presence of @Validated ?
+ if (ann instanceof Validated) {
+ return ((Validated) ann).value();
+ }
+ // Direct presence of @Valid ?
Class extends Annotation> annotationType = ann.annotationType();
- String annotationName = annotationType.getName();
- if ("javax.validation.Valid".equals(annotationName)) {
+ if ("javax.validation.Valid".equals(annotationType.getName())) {
return EMPTY_OBJECT_ARRAY;
}
+ // Meta presence of @Validated ?
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null) {
- Object hints = validatedAnn.value();
- return convertValidationHints(hints);
+ return validatedAnn.value();
}
+ // Custom validation annotation ?
if (annotationType.getSimpleName().startsWith("Valid")) {
- Object hints = AnnotationUtils.getValue(ann);
- return convertValidationHints(hints);
+ return convertValidationHints(AnnotationUtils.getValue(ann));
}
+ // No validation triggered
return null;
}
diff --git a/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java b/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java
index 7b4911a8b49a..31e2d02a40d2 100644
--- a/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java
+++ b/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -118,6 +118,7 @@ void spr13081ConfigNoCacheNameIsRequired() {
assertThat(cacheResolver.getCache("foo").get("foo")).isNull();
Object result = bean.getSimple("foo"); // cache name = id
assertThat(cacheResolver.getCache("foo").get("foo").get()).isEqualTo(result);
+
context.close();
}
@@ -127,7 +128,7 @@ void spr13081ConfigFailIfCacheResolverReturnsNullCacheName() {
Spr13081Service bean = context.getBean(Spr13081Service.class);
assertThatIllegalStateException().isThrownBy(() -> bean.getSimple(null))
- .withMessageContaining(MyCacheResolver.class.getName());
+ .withMessageContaining(MyCacheResolver.class.getName());
context.close();
}
@@ -146,6 +147,7 @@ void spr14230AdaptsToOptional() {
TestBean tb2 = bean.findById("tb1").get();
assertThat(tb2).isNotSameAs(tb);
assertThat(cache.get("tb1").get()).isSameAs(tb2);
+
context.close();
}
@@ -177,6 +179,7 @@ void spr15271FindsOnInterfaceWithInterfaceProxy() {
bean.insertItem(tb);
assertThat(bean.findById("tb1").get()).isSameAs(tb);
assertThat(cache.get("tb1").get()).isSameAs(tb);
+
context.close();
}
@@ -190,6 +193,7 @@ void spr15271FindsOnInterfaceWithCglibProxy() {
bean.insertItem(tb);
assertThat(bean.findById("tb1").get()).isSameAs(tb);
assertThat(cache.get("tb1").get()).isSameAs(tb);
+
context.close();
}
diff --git a/spring-context/src/test/java/org/springframework/cache/interceptor/CacheSyncFailureTests.java b/spring-context/src/test/java/org/springframework/cache/interceptor/CacheSyncFailureTests.java
index de4776adae2e..56b22970d1ac 100644
--- a/spring-context/src/test/java/org/springframework/cache/interceptor/CacheSyncFailureTests.java
+++ b/spring-context/src/test/java/org/springframework/cache/interceptor/CacheSyncFailureTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,8 +48,9 @@ public class CacheSyncFailureTests {
private SimpleService simpleService;
+
@BeforeEach
- public void setUp() {
+ public void setup() {
this.context = new AnnotationConfigApplicationContext(Config.class);
this.simpleService = this.context.getBean(SimpleService.class);
}
@@ -61,39 +62,40 @@ public void closeContext() {
}
}
+
@Test
public void unlessSync() {
- assertThatIllegalStateException().isThrownBy(() ->
- this.simpleService.unlessSync("key"))
- .withMessageContaining("@Cacheable(sync=true) does not support unless attribute");
+ assertThatIllegalStateException()
+ .isThrownBy(() -> this.simpleService.unlessSync("key"))
+ .withMessageContaining("A sync=true operation does not support the unless attribute");
}
@Test
public void severalCachesSync() {
- assertThatIllegalStateException().isThrownBy(() ->
- this.simpleService.severalCachesSync("key"))
- .withMessageContaining("@Cacheable(sync=true) only allows a single cache");
+ assertThatIllegalStateException()
+ .isThrownBy(() -> this.simpleService.severalCachesSync("key"))
+ .withMessageContaining("A sync=true operation is restricted to a single cache");
}
@Test
public void severalCachesWithResolvedSync() {
- assertThatIllegalStateException().isThrownBy(() ->
- this.simpleService.severalCachesWithResolvedSync("key"))
- .withMessageContaining("@Cacheable(sync=true) only allows a single cache");
+ assertThatIllegalStateException()
+ .isThrownBy(() -> this.simpleService.severalCachesWithResolvedSync("key"))
+ .withMessageContaining("A sync=true operation is restricted to a single cache");
}
@Test
public void syncWithAnotherOperation() {
- assertThatIllegalStateException().isThrownBy(() ->
- this.simpleService.syncWithAnotherOperation("key"))
- .withMessageContaining("@Cacheable(sync=true) cannot be combined with other cache operations");
+ assertThatIllegalStateException()
+ .isThrownBy(() -> this.simpleService.syncWithAnotherOperation("key"))
+ .withMessageContaining("A sync=true operation cannot be combined with other cache operations");
}
@Test
public void syncWithTwoGetOperations() {
- assertThatIllegalStateException().isThrownBy(() ->
- this.simpleService.syncWithTwoGetOperations("key"))
- .withMessageContaining("Only one @Cacheable(sync=true) entry is allowed");
+ assertThatIllegalStateException()
+ .isThrownBy(() -> this.simpleService.syncWithTwoGetOperations("key"))
+ .withMessageContaining("Only one sync=true operation is allowed");
}
@@ -131,6 +133,7 @@ public Object syncWithTwoGetOperations(Object arg1) {
}
}
+
@Configuration
@EnableCaching
static class Config implements CachingConfigurer {
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java
index 76b9fc53dd62..617b5579b7a6 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java
@@ -197,6 +197,7 @@ public void testSimpleScanWithDefaultFiltersAndOverridingBean() {
context.registerBeanDefinition("stubFooDao", new RootBeanDefinition(TestBean.class));
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.setIncludeAnnotationConfig(false);
+
// should not fail!
scanner.scan(BASE_PACKAGE);
}
@@ -207,6 +208,7 @@ public void testSimpleScanWithDefaultFiltersAndDefaultBeanNameClash() {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.setIncludeAnnotationConfig(false);
scanner.scan("org.springframework.context.annotation3");
+
assertThatIllegalStateException().isThrownBy(() -> scanner.scan(BASE_PACKAGE))
.withMessageContaining("stubFooDao")
.withMessageContaining(StubFooDao.class.getName());
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/EnableLoadTimeWeavingTests.java b/spring-context/src/test/java/org/springframework/context/annotation/EnableLoadTimeWeavingTests.java
index 42ff4229f3c2..95de2db864c6 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/EnableLoadTimeWeavingTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/EnableLoadTimeWeavingTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java b/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java
index c493f075b1ae..4374b4210d59 100644
--- a/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java
+++ b/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -164,12 +164,12 @@ void contextEventsAreReceived() {
ContextEventListener listener = this.context.getBean(ContextEventListener.class);
List events = this.eventCollector.getEvents(listener);
- assertThat(events.size()).as("Wrong number of initial context events").isEqualTo(1);
+ assertThat(events).as("Wrong number of initial context events").hasSize(1);
assertThat(events.get(0).getClass()).isEqualTo(ContextRefreshedEvent.class);
this.context.stop();
List eventsAfterStop = this.eventCollector.getEvents(listener);
- assertThat(eventsAfterStop.size()).as("Wrong number of context events on shutdown").isEqualTo(2);
+ assertThat(eventsAfterStop).as("Wrong number of context events on shutdown").hasSize(2);
assertThat(eventsAfterStop.get(1).getClass()).isEqualTo(ContextStoppedEvent.class);
this.eventCollector.assertTotalEventsCount(2);
}
@@ -334,7 +334,7 @@ void eventListenerWorksWithSimpleInterfaceProxy() {
load(ScopedProxyTestBean.class);
SimpleService proxy = this.context.getBean(SimpleService.class);
- assertThat(proxy instanceof Advised).as("bean should be a proxy").isTrue();
+ assertThat(proxy).as("bean should be a proxy").isInstanceOf(Advised.class);
this.eventCollector.assertNoEventReceived(proxy.getId());
this.context.publishEvent(new ContextRefreshedEvent(this.context));
@@ -351,7 +351,7 @@ void eventListenerWorksWithAnnotatedInterfaceProxy() {
load(AnnotatedProxyTestBean.class);
AnnotatedSimpleService proxy = this.context.getBean(AnnotatedSimpleService.class);
- assertThat(proxy instanceof Advised).as("bean should be a proxy").isTrue();
+ assertThat(proxy).as("bean should be a proxy").isInstanceOf(Advised.class);
this.eventCollector.assertNoEventReceived(proxy.getId());
this.context.publishEvent(new ContextRefreshedEvent(this.context));
@@ -517,7 +517,6 @@ void replyWithPayload() {
ReplyEventListener replyEventListener = this.context.getBean(ReplyEventListener.class);
TestEventListener listener = this.context.getBean(TestEventListener.class);
-
this.eventCollector.assertNoEventReceived(listener);
this.eventCollector.assertNoEventReceived(replyEventListener);
this.context.publishEvent(event);
@@ -634,6 +633,17 @@ void orderedListeners() {
assertThat(listener.order).contains("first", "second", "third");
}
+ @Test
+ void publicSubclassWithInheritedEventListener() {
+ load(PublicSubclassWithInheritedEventListener.class);
+ TestEventListener listener = this.context.getBean(PublicSubclassWithInheritedEventListener.class);
+
+ this.eventCollector.assertNoEventReceived(listener);
+ this.context.publishEvent("test");
+ this.eventCollector.assertEvent(listener, "test");
+ this.eventCollector.assertTotalEventsCount(1);
+ }
+
@Test @Disabled // SPR-15122
void listenersReceiveEarlyEvents() {
load(EventOnPostConstruct.class, OrderedTestListener.class);
@@ -646,7 +656,7 @@ void listenersReceiveEarlyEvents() {
void missingListenerBeanIgnored() {
load(MissingEventListener.class);
context.getBean(UseMissingEventListener.class);
- context.getBean(ApplicationEventMulticaster.class).multicastEvent(new TestEvent(this));
+ context.publishEvent(new TestEvent(this));
}
@@ -753,7 +763,6 @@ static class ContextEventListener extends AbstractTestEventListener {
public void handleContextEvent(ApplicationContextEvent event) {
collectEvent(event);
}
-
}
@@ -980,7 +989,6 @@ public void handleString(GenericEventPojo value) {
}
-
@EventListener
@Retention(RetentionPolicy.RUNTIME)
public @interface ConditionalEvent {
@@ -1032,7 +1040,7 @@ public void handleRatio(Double ratio) {
}
- @Configuration
+ @Component
static class OrderedTestListener extends TestEventListener {
public final List order = new ArrayList<>();
@@ -1056,6 +1064,11 @@ public void handleSecond(String payload) {
}
+ @Component
+ public static class PublicSubclassWithInheritedEventListener extends TestEventListener {
+ }
+
+
static class EventOnPostConstruct {
@Autowired
diff --git a/spring-context/src/test/java/org/springframework/mock/env/MockEnvironment.java b/spring-context/src/test/java/org/springframework/mock/env/MockEnvironment.java
index 6397865a1f88..9a533f563570 100644
--- a/spring-context/src/test/java/org/springframework/mock/env/MockEnvironment.java
+++ b/spring-context/src/test/java/org/springframework/mock/env/MockEnvironment.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java
index 25f59ac8bddc..dc718fdeacef 100644
--- a/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java
+++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableSchedulingTests.java
@@ -63,7 +63,7 @@ public void withFixedRateTask() throws InterruptedException {
ctx = new AnnotationConfigApplicationContext(FixedRateTaskConfig.class);
assertThat(ctx.getBean(ScheduledTaskHolder.class).getScheduledTasks()).hasSize(2);
- Thread.sleep(100);
+ Thread.sleep(110);
assertThat(ctx.getBean(AtomicInteger.class).get()).isGreaterThanOrEqualTo(10);
}
@@ -73,7 +73,7 @@ public void withSubclass() throws InterruptedException {
ctx = new AnnotationConfigApplicationContext(FixedRateTaskConfigSubclass.class);
assertThat(ctx.getBean(ScheduledTaskHolder.class).getScheduledTasks()).hasSize(2);
- Thread.sleep(100);
+ Thread.sleep(110);
assertThat(ctx.getBean(AtomicInteger.class).get()).isGreaterThanOrEqualTo(10);
}
@@ -83,7 +83,7 @@ public void withExplicitScheduler() throws InterruptedException {
ctx = new AnnotationConfigApplicationContext(ExplicitSchedulerConfig.class);
assertThat(ctx.getBean(ScheduledTaskHolder.class).getScheduledTasks()).hasSize(1);
- Thread.sleep(100);
+ Thread.sleep(110);
assertThat(ctx.getBean(AtomicInteger.class).get()).isGreaterThanOrEqualTo(10);
assertThat(ctx.getBean(ExplicitSchedulerConfig.class).threadName).startsWith("explicitScheduler-");
assertThat(Arrays.asList(ctx.getDefaultListableBeanFactory().getDependentBeans("myTaskScheduler")).contains(
@@ -102,7 +102,7 @@ public void withExplicitScheduledTaskRegistrar() throws InterruptedException {
ctx = new AnnotationConfigApplicationContext(ExplicitScheduledTaskRegistrarConfig.class);
assertThat(ctx.getBean(ScheduledTaskHolder.class).getScheduledTasks()).hasSize(1);
- Thread.sleep(100);
+ Thread.sleep(110);
assertThat(ctx.getBean(AtomicInteger.class).get()).isGreaterThanOrEqualTo(10);
assertThat(ctx.getBean(ExplicitScheduledTaskRegistrarConfig.class).threadName).startsWith("explicitScheduler1");
}
@@ -124,7 +124,7 @@ public void withAmbiguousTaskSchedulers_andSingleTask_disambiguatedByScheduledTa
ctx = new AnnotationConfigApplicationContext(
SchedulingEnabled_withAmbiguousTaskSchedulers_andSingleTask_disambiguatedByScheduledTaskRegistrar.class);
- Thread.sleep(100);
+ Thread.sleep(110);
assertThat(ctx.getBean(ThreadAwareWorker.class).executedByThread).startsWith("explicitScheduler2-");
}
@@ -134,7 +134,7 @@ public void withAmbiguousTaskSchedulers_andSingleTask_disambiguatedBySchedulerNa
ctx = new AnnotationConfigApplicationContext(
SchedulingEnabled_withAmbiguousTaskSchedulers_andSingleTask_disambiguatedBySchedulerNameAttribute.class);
- Thread.sleep(100);
+ Thread.sleep(110);
assertThat(ctx.getBean(ThreadAwareWorker.class).executedByThread).startsWith("explicitScheduler2-");
}
@@ -143,7 +143,7 @@ public void withAmbiguousTaskSchedulers_andSingleTask_disambiguatedBySchedulerNa
public void withTaskAddedVia_configureTasks() throws InterruptedException {
ctx = new AnnotationConfigApplicationContext(SchedulingEnabled_withTaskAddedVia_configureTasks.class);
- Thread.sleep(100);
+ Thread.sleep(110);
assertThat(ctx.getBean(ThreadAwareWorker.class).executedByThread).startsWith("taskScheduler-");
}
@@ -152,7 +152,7 @@ public void withTaskAddedVia_configureTasks() throws InterruptedException {
public void withTriggerTask() throws InterruptedException {
ctx = new AnnotationConfigApplicationContext(TriggerTaskConfig.class);
- Thread.sleep(100);
+ Thread.sleep(110);
assertThat(ctx.getBean(AtomicInteger.class).get()).isGreaterThan(1);
}
diff --git a/spring-context/src/test/java/org/springframework/scheduling/concurrent/ScheduledExecutorFactoryBeanTests.java b/spring-context/src/test/java/org/springframework/scheduling/concurrent/ScheduledExecutorFactoryBeanTests.java
index 610fd595c435..6be90f35ce6a 100644
--- a/spring-context/src/test/java/org/springframework/scheduling/concurrent/ScheduledExecutorFactoryBeanTests.java
+++ b/spring-context/src/test/java/org/springframework/scheduling/concurrent/ScheduledExecutorFactoryBeanTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java b/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java
index 546c599c01f7..037dc8d214a3 100644
--- a/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java
+++ b/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -95,11 +95,11 @@ void bindingNoErrors() throws BindException {
binder.bind(pvs);
binder.close();
- assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
- assertThat(rod.getAge() == 32).as("changed age correctly").isTrue();
+ assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
+ assertThat(rod.getAge()).as("changed age correctly").isEqualTo(32);
Map, ?> map = binder.getBindingResult().getModel();
- assertThat(map.size() == 2).as("There is one element in map").isTrue();
+ assertThat(map).as("There is one element in map").hasSize(2);
TestBean tb = (TestBean) map.get("person");
assertThat(tb.equals(rod)).as("Same object").isTrue();
@@ -157,8 +157,9 @@ void bindingNoErrorsNotIgnoreUnknown() {
pvs.add("name", "Rod");
pvs.add("age", 32);
pvs.add("nonExisting", "someValue");
- assertThatExceptionOfType(NotWritablePropertyException.class).isThrownBy(() ->
- binder.bind(pvs));
+
+ assertThatExceptionOfType(NotWritablePropertyException.class)
+ .isThrownBy(() -> binder.bind(pvs));
}
@Test
@@ -168,8 +169,9 @@ void bindingNoErrorsWithInvalidField() {
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("name", "Rod");
pvs.add("spouse.age", 32);
- assertThatExceptionOfType(NullValueInNestedPathException.class).isThrownBy(() ->
- binder.bind(pvs));
+
+ assertThatExceptionOfType(NullValueInNestedPathException.class)
+ .isThrownBy(() -> binder.bind(pvs));
}
@Test
@@ -197,57 +199,56 @@ void bindingWithErrors() {
pvs.add("age", "32x");
pvs.add("touchy", "m.y");
binder.bind(pvs);
- assertThatExceptionOfType(BindException.class).isThrownBy(
- binder::close)
- .satisfies(ex -> {
- assertThat(rod.getName()).isEqualTo("Rod");
- Map, ?> map = binder.getBindingResult().getModel();
- TestBean tb = (TestBean) map.get("person");
- assertThat(tb).isSameAs(rod);
-
- BindingResult br = (BindingResult) map.get(BindingResult.MODEL_KEY_PREFIX + "person");
- assertThat(BindingResultUtils.getBindingResult(map, "person")).isEqualTo(br);
- assertThat(BindingResultUtils.getRequiredBindingResult(map, "person")).isEqualTo(br);
-
- assertThat(BindingResultUtils.getBindingResult(map, "someOtherName")).isNull();
- assertThatIllegalStateException().isThrownBy(() ->
- BindingResultUtils.getRequiredBindingResult(map, "someOtherName"));
-
- assertThat(binder.getBindingResult()).as("Added itself to map").isSameAs(br);
- assertThat(br.hasErrors()).isTrue();
- assertThat(br.getErrorCount()).isEqualTo(2);
-
- assertThat(br.hasFieldErrors("age")).isTrue();
- assertThat(br.getFieldErrorCount("age")).isEqualTo(1);
- assertThat(binder.getBindingResult().getFieldValue("age")).isEqualTo("32x");
- FieldError ageError = binder.getBindingResult().getFieldError("age");
- assertThat(ageError).isNotNull();
- assertThat(ageError.getCode()).isEqualTo("typeMismatch");
- assertThat(ageError.getRejectedValue()).isEqualTo("32x");
- assertThat(ageError.contains(TypeMismatchException.class)).isTrue();
- assertThat(ageError.contains(NumberFormatException.class)).isTrue();
- assertThat(ageError.unwrap(NumberFormatException.class).getMessage()).contains("32x");
- assertThat(tb.getAge()).isEqualTo(0);
-
- assertThat(br.hasFieldErrors("touchy")).isTrue();
- assertThat(br.getFieldErrorCount("touchy")).isEqualTo(1);
- assertThat(binder.getBindingResult().getFieldValue("touchy")).isEqualTo("m.y");
- FieldError touchyError = binder.getBindingResult().getFieldError("touchy");
- assertThat(touchyError).isNotNull();
- assertThat(touchyError.getCode()).isEqualTo("methodInvocation");
- assertThat(touchyError.getRejectedValue()).isEqualTo("m.y");
- assertThat(touchyError.contains(MethodInvocationException.class)).isTrue();
- assertThat(touchyError.unwrap(MethodInvocationException.class).getCause().getMessage()).contains("a .");
- assertThat(tb.getTouchy()).isNull();
-
- DataBinder binder2 = new DataBinder(new TestBean(), "person");
- MutablePropertyValues pvs2 = new MutablePropertyValues();
- pvs2.add("name", "Rod");
- pvs2.add("age", "32x");
- pvs2.add("touchy", "m.y");
- binder2.bind(pvs2);
- assertThat(ex.getBindingResult()).isEqualTo(binder2.getBindingResult());
- });
+
+ assertThatExceptionOfType(BindException.class).isThrownBy(binder::close).satisfies(ex -> {
+ assertThat(rod.getName()).isEqualTo("Rod");
+ Map, ?> map = binder.getBindingResult().getModel();
+ TestBean tb = (TestBean) map.get("person");
+ assertThat(tb).isSameAs(rod);
+
+ BindingResult br = (BindingResult) map.get(BindingResult.MODEL_KEY_PREFIX + "person");
+ assertThat(BindingResultUtils.getBindingResult(map, "person")).isEqualTo(br);
+ assertThat(BindingResultUtils.getRequiredBindingResult(map, "person")).isEqualTo(br);
+
+ assertThat(BindingResultUtils.getBindingResult(map, "someOtherName")).isNull();
+ assertThatIllegalStateException().isThrownBy(() ->
+ BindingResultUtils.getRequiredBindingResult(map, "someOtherName"));
+
+ assertThat(binder.getBindingResult()).as("Added itself to map").isSameAs(br);
+ assertThat(br.hasErrors()).isTrue();
+ assertThat(br.getErrorCount()).isEqualTo(2);
+
+ assertThat(br.hasFieldErrors("age")).isTrue();
+ assertThat(br.getFieldErrorCount("age")).isEqualTo(1);
+ assertThat(binder.getBindingResult().getFieldValue("age")).isEqualTo("32x");
+ FieldError ageError = binder.getBindingResult().getFieldError("age");
+ assertThat(ageError).isNotNull();
+ assertThat(ageError.getCode()).isEqualTo("typeMismatch");
+ assertThat(ageError.getRejectedValue()).isEqualTo("32x");
+ assertThat(ageError.contains(TypeMismatchException.class)).isTrue();
+ assertThat(ageError.contains(NumberFormatException.class)).isTrue();
+ assertThat(ageError.unwrap(NumberFormatException.class).getMessage()).contains("32x");
+ assertThat(tb.getAge()).isEqualTo(0);
+
+ assertThat(br.hasFieldErrors("touchy")).isTrue();
+ assertThat(br.getFieldErrorCount("touchy")).isEqualTo(1);
+ assertThat(binder.getBindingResult().getFieldValue("touchy")).isEqualTo("m.y");
+ FieldError touchyError = binder.getBindingResult().getFieldError("touchy");
+ assertThat(touchyError).isNotNull();
+ assertThat(touchyError.getCode()).isEqualTo("methodInvocation");
+ assertThat(touchyError.getRejectedValue()).isEqualTo("m.y");
+ assertThat(touchyError.contains(MethodInvocationException.class)).isTrue();
+ assertThat(touchyError.unwrap(MethodInvocationException.class).getCause().getMessage()).contains("a .");
+ assertThat(tb.getTouchy()).isNull();
+
+ DataBinder binder2 = new DataBinder(new TestBean(), "person");
+ MutablePropertyValues pvs2 = new MutablePropertyValues();
+ pvs2.add("name", "Rod");
+ pvs2.add("age", "32x");
+ pvs2.add("touchy", "m.y");
+ binder2.bind(pvs2);
+ assertThat(ex.getBindingResult()).isEqualTo(binder2.getBindingResult());
+ });
}
@Test
@@ -257,15 +258,17 @@ void bindingWithSystemFieldError() {
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("class.classLoader.URLs[0]", "https://myserver");
binder.setIgnoreUnknownFields(false);
- assertThatExceptionOfType(NotWritablePropertyException.class).isThrownBy(() ->
- binder.bind(pvs))
- .withMessageContaining("classLoader");
+
+ assertThatExceptionOfType(NotWritablePropertyException.class)
+ .isThrownBy(() -> binder.bind(pvs))
+ .withMessageContaining("classLoader");
}
@Test
void bindingWithErrorsAndCustomEditors() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
+
binder.registerCustomEditor(String.class, "touchy", new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws IllegalArgumentException {
@@ -286,6 +289,7 @@ public String getAsText() {
return ((TestBean) getValue()).getName();
}
});
+
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("name", "Rod");
pvs.add("age", "32x");
@@ -293,41 +297,39 @@ public String getAsText() {
pvs.add("spouse", "Kerry");
binder.bind(pvs);
- assertThatExceptionOfType(BindException.class).isThrownBy(
- binder::close)
- .satisfies(ex -> {
- assertThat(rod.getName()).isEqualTo("Rod");
- Map, ?> model = binder.getBindingResult().getModel();
- TestBean tb = (TestBean) model.get("person");
- assertThat(tb).isEqualTo(rod);
-
- BindingResult br = (BindingResult) model.get(BindingResult.MODEL_KEY_PREFIX + "person");
- assertThat(binder.getBindingResult()).isSameAs(br);
- assertThat(br.hasErrors()).isTrue();
- assertThat(br.getErrorCount()).isEqualTo(2);
-
- assertThat(br.hasFieldErrors("age")).isTrue();
- assertThat(br.getFieldErrorCount("age")).isEqualTo(1);
- assertThat(binder.getBindingResult().getFieldValue("age")).isEqualTo("32x");
- FieldError ageError = binder.getBindingResult().getFieldError("age");
- assertThat(ageError).isNotNull();
- assertThat(ageError.getCode()).isEqualTo("typeMismatch");
- assertThat(ageError.getRejectedValue()).isEqualTo("32x");
- assertThat(tb.getAge()).isEqualTo(0);
-
- assertThat(br.hasFieldErrors("touchy")).isTrue();
- assertThat(br.getFieldErrorCount("touchy")).isEqualTo(1);
- assertThat(binder.getBindingResult().getFieldValue("touchy")).isEqualTo("m.y");
- FieldError touchyError = binder.getBindingResult().getFieldError("touchy");
- assertThat(touchyError).isNotNull();
- assertThat(touchyError.getCode()).isEqualTo("methodInvocation");
- assertThat(touchyError.getRejectedValue()).isEqualTo("m.y");
- assertThat(tb.getTouchy()).isNull();
-
- assertThat(br.hasFieldErrors("spouse")).isFalse();
- assertThat(binder.getBindingResult().getFieldValue("spouse")).isEqualTo("Kerry");
- assertThat(tb.getSpouse()).isNotNull();
- });
+ assertThatExceptionOfType(BindException.class).isThrownBy(binder::close).satisfies(ex -> {
+ assertThat(rod.getName()).isEqualTo("Rod");
+ Map, ?> model = binder.getBindingResult().getModel();
+ TestBean tb = (TestBean) model.get("person");
+ assertThat(tb).isEqualTo(rod);
+
+ BindingResult br = (BindingResult) model.get(BindingResult.MODEL_KEY_PREFIX + "person");
+ assertThat(binder.getBindingResult()).isSameAs(br);
+ assertThat(br.hasErrors()).isTrue();
+ assertThat(br.getErrorCount()).isEqualTo(2);
+
+ assertThat(br.hasFieldErrors("age")).isTrue();
+ assertThat(br.getFieldErrorCount("age")).isEqualTo(1);
+ assertThat(binder.getBindingResult().getFieldValue("age")).isEqualTo("32x");
+ FieldError ageError = binder.getBindingResult().getFieldError("age");
+ assertThat(ageError).isNotNull();
+ assertThat(ageError.getCode()).isEqualTo("typeMismatch");
+ assertThat(ageError.getRejectedValue()).isEqualTo("32x");
+ assertThat(tb.getAge()).isEqualTo(0);
+
+ assertThat(br.hasFieldErrors("touchy")).isTrue();
+ assertThat(br.getFieldErrorCount("touchy")).isEqualTo(1);
+ assertThat(binder.getBindingResult().getFieldValue("touchy")).isEqualTo("m.y");
+ FieldError touchyError = binder.getBindingResult().getFieldError("touchy");
+ assertThat(touchyError).isNotNull();
+ assertThat(touchyError.getCode()).isEqualTo("methodInvocation");
+ assertThat(touchyError.getRejectedValue()).isEqualTo("m.y");
+ assertThat(tb.getTouchy()).isNull();
+
+ assertThat(br.hasFieldErrors("spouse")).isFalse();
+ assertThat(binder.getBindingResult().getFieldValue("spouse")).isEqualTo("Kerry");
+ assertThat(tb.getSpouse()).isNotNull();
+ });
}
@Test
@@ -576,7 +578,7 @@ void bindingWithCustomFormatter() {
editor = binder.getBindingResult().findEditor("myFloat", null);
assertThat(editor).isNotNull();
editor.setAsText("1,6");
- assertThat(((Number) editor.getValue()).floatValue() == 1.6f).isTrue();
+ assertThat(((Number) editor.getValue()).floatValue()).isEqualTo(1.6f);
}
finally {
LocaleContextHolder.resetLocaleContext();
@@ -752,15 +754,15 @@ void bindingWithAllowedFieldsUsingAsterisks() throws BindException {
binder.bind(pvs);
binder.close();
- assertThat("Rod".equals(rod.getName())).as("changed name correctly").isTrue();
- assertThat("Rod".equals(rod.getTouchy())).as("changed touchy correctly").isTrue();
- assertThat(rod.getAge() == 0).as("did not change age").isTrue();
+ assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
+ assertThat(rod.getTouchy()).as("changed touchy correctly").isEqualTo("Rod");
+ assertThat(rod.getAge()).as("did not change age").isEqualTo(0);
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
assertThat(disallowedFields).hasSize(1);
assertThat(disallowedFields[0]).isEqualTo("age");
Map,?> m = binder.getBindingResult().getModel();
- assertThat(m.size() == 2).as("There is one element in map").isTrue();
+ assertThat(m).as("There is one element in map").hasSize(2);
TestBean tb = (TestBean) m.get("person");
assertThat(tb.equals(rod)).as("Same object").isTrue();
}
@@ -914,7 +916,7 @@ public String getAsText() {
binder.getBindingResult().rejectValue("touchy", "someCode", "someMessage");
binder.getBindingResult().rejectValue("spouse.name", "someCode", "someMessage");
- assertThat(binder.getBindingResult().getNestedPath()).isEqualTo("");
+ assertThat(binder.getBindingResult().getNestedPath()).isEmpty();
assertThat(binder.getBindingResult().getFieldValue("name")).isEqualTo("value");
assertThat(binder.getBindingResult().getFieldError("name").getRejectedValue()).isEqualTo("prefixvalue");
assertThat(tb.getName()).isEqualTo("prefixvalue");
@@ -1010,7 +1012,7 @@ public String print(String object, Locale locale) {
binder.getBindingResult().rejectValue("touchy", "someCode", "someMessage");
binder.getBindingResult().rejectValue("spouse.name", "someCode", "someMessage");
- assertThat(binder.getBindingResult().getNestedPath()).isEqualTo("");
+ assertThat(binder.getBindingResult().getNestedPath()).isEmpty();
assertThat(binder.getBindingResult().getFieldValue("name")).isEqualTo("value");
assertThat(binder.getBindingResult().getFieldError("name").getRejectedValue()).isEqualTo("prefixvalue");
assertThat(tb.getName()).isEqualTo("prefixvalue");
@@ -1134,12 +1136,11 @@ void validatorNoErrors() throws Exception {
tb2.setAge(34);
tb.setSpouse(tb2);
DataBinder db = new DataBinder(tb, "tb");
+ db.setValidator(new TestBeanValidator());
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("spouse.age", "argh");
db.bind(pvs);
Errors errors = db.getBindingResult();
- Validator testValidator = new TestBeanValidator();
- testValidator.validate(tb, errors);
errors.setNestedPath("spouse");
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
@@ -1148,7 +1149,7 @@ void validatorNoErrors() throws Exception {
spouseValidator.validate(tb.getSpouse(), errors);
errors.setNestedPath("");
- assertThat(errors.getNestedPath()).isEqualTo("");
+ assertThat(errors.getNestedPath()).isEmpty();
errors.pushNestedPath("spouse");
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
errors.pushNestedPath("spouse");
@@ -1156,7 +1157,7 @@ void validatorNoErrors() throws Exception {
errors.popNestedPath();
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
errors.popNestedPath();
- assertThat(errors.getNestedPath()).isEqualTo("");
+ assertThat(errors.getNestedPath()).isEmpty();
try {
errors.popNestedPath();
}
@@ -1166,7 +1167,7 @@ void validatorNoErrors() throws Exception {
errors.pushNestedPath("spouse");
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
errors.setNestedPath("");
- assertThat(errors.getNestedPath()).isEqualTo("");
+ assertThat(errors.getNestedPath()).isEmpty();
try {
errors.popNestedPath();
}
@@ -1187,8 +1188,7 @@ void validatorNoErrors() throws Exception {
void validatorWithErrors() {
TestBean tb = new TestBean();
tb.setSpouse(new TestBean());
-
- Errors errors = new BeanPropertyBindingResult(tb, "tb");
+ Errors errors = new DataBinder(tb, "tb").getBindingResult();
Validator testValidator = new TestBeanValidator();
testValidator.validate(tb, errors);
@@ -1201,7 +1201,11 @@ void validatorWithErrors() {
errors.setNestedPath("");
assertThat(errors.hasErrors()).isTrue();
assertThat(errors.getErrorCount()).isEqualTo(6);
+ assertThat(errors.getAllErrors())
+ .containsAll(errors.getGlobalErrors())
+ .containsAll(errors.getFieldErrors());
+ assertThat(errors.hasGlobalErrors()).isTrue();
assertThat(errors.getGlobalErrorCount()).isEqualTo(2);
assertThat(errors.getGlobalError().getCode()).isEqualTo("NAME_TOUCHY_MISMATCH");
assertThat((errors.getGlobalErrors().get(0)).getCode()).isEqualTo("NAME_TOUCHY_MISMATCH");
@@ -1257,10 +1261,11 @@ void validatorWithErrorsAndCodesPrefix() {
TestBean tb = new TestBean();
tb.setSpouse(new TestBean());
- BeanPropertyBindingResult errors = new BeanPropertyBindingResult(tb, "tb");
+ DataBinder dataBinder = new DataBinder(tb, "tb");
DefaultMessageCodesResolver codesResolver = new DefaultMessageCodesResolver();
codesResolver.setPrefix("validation.");
- errors.setMessageCodesResolver(codesResolver);
+ dataBinder.setMessageCodesResolver(codesResolver);
+ Errors errors = dataBinder.getBindingResult();
Validator testValidator = new TestBeanValidator();
testValidator.validate(tb, errors);
@@ -1273,7 +1278,11 @@ void validatorWithErrorsAndCodesPrefix() {
errors.setNestedPath("");
assertThat(errors.hasErrors()).isTrue();
assertThat(errors.getErrorCount()).isEqualTo(6);
+ assertThat(errors.getAllErrors())
+ .containsAll(errors.getGlobalErrors())
+ .containsAll(errors.getFieldErrors());
+ assertThat(errors.hasGlobalErrors()).isTrue();
assertThat(errors.getGlobalErrorCount()).isEqualTo(2);
assertThat(errors.getGlobalError().getCode()).isEqualTo("validation.NAME_TOUCHY_MISMATCH");
assertThat((errors.getGlobalErrors().get(0)).getCode()).isEqualTo("validation.NAME_TOUCHY_MISMATCH");
@@ -1327,9 +1336,11 @@ void validatorWithErrorsAndCodesPrefix() {
@Test
void validatorWithNestedObjectNull() {
TestBean tb = new TestBean();
- Errors errors = new BeanPropertyBindingResult(tb, "tb");
+ Errors errors = new DataBinder(tb, "tb").getBindingResult();
+
Validator testValidator = new TestBeanValidator();
testValidator.validate(tb, errors);
+
errors.setNestedPath("spouse.");
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
Validator spouseValidator = new SpouseValidator();
@@ -1724,7 +1735,7 @@ public void setAsText(String text) throws IllegalArgumentException {
pvs.add("stringArray", new String[] {"a1", "b2"});
binder.bind(pvs);
assertThat(binder.getBindingResult().hasErrors()).isFalse();
- assertThat(tb.getStringArray().length).isEqualTo(2);
+ assertThat(tb.getStringArray()).hasSize(2);
assertThat(tb.getStringArray()[0]).isEqualTo("Xa1");
assertThat(tb.getStringArray()[1]).isEqualTo("Xb2");
}
@@ -1800,16 +1811,16 @@ void rejectWithoutDefaultMessage() {
tb.setName("myName");
tb.setAge(99);
- BeanPropertyBindingResult ex = new BeanPropertyBindingResult(tb, "tb");
- ex.reject("invalid");
- ex.rejectValue("age", "invalidField");
+ BeanPropertyBindingResult errors = new BeanPropertyBindingResult(tb, "tb");
+ errors.reject("invalid");
+ errors.rejectValue("age", "invalidField");
StaticMessageSource ms = new StaticMessageSource();
ms.addMessage("invalid", Locale.US, "general error");
ms.addMessage("invalidField", Locale.US, "invalid field");
- assertThat(ms.getMessage(ex.getGlobalError(), Locale.US)).isEqualTo("general error");
- assertThat(ms.getMessage(ex.getFieldError("age"), Locale.US)).isEqualTo("invalid field");
+ assertThat(ms.getMessage(errors.getGlobalError(), Locale.US)).isEqualTo("general error");
+ assertThat(ms.getMessage(errors.getFieldError("age"), Locale.US)).isEqualTo("invalid field");
}
@Test
@@ -1877,13 +1888,13 @@ void autoGrowWithinDefaultLimit() {
void autoGrowBeyondDefaultLimit() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
-
MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("friends[256]", "");
+
assertThatExceptionOfType(InvalidPropertyException.class)
- .isThrownBy(() -> binder.bind(mpvs))
- .havingRootCause()
- .isInstanceOf(IndexOutOfBoundsException.class);
+ .isThrownBy(() -> binder.bind(mpvs))
+ .havingRootCause()
+ .isInstanceOf(IndexOutOfBoundsException.class);
}
@Test
@@ -1904,13 +1915,13 @@ void autoGrowBeyondCustomLimit() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.setAutoGrowCollectionLimit(10);
-
MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("friends[16]", "");
+
assertThatExceptionOfType(InvalidPropertyException.class)
- .isThrownBy(() -> binder.bind(mpvs))
- .havingRootCause()
- .isInstanceOf(IndexOutOfBoundsException.class);
+ .isThrownBy(() -> binder.bind(mpvs))
+ .havingRootCause()
+ .isInstanceOf(IndexOutOfBoundsException.class);
}
@Test
@@ -1926,7 +1937,7 @@ void nestedGrowingList() {
List list = (List) form.getF().get("list");
assertThat(list.get(0)).isEqualTo("firstValue");
assertThat(list.get(1)).isEqualTo("secondValue");
- assertThat(list.size()).isEqualTo(2);
+ assertThat(list).hasSize(2);
}
@Test
@@ -1959,7 +1970,7 @@ void setAutoGrowCollectionLimit() {
pvs.add("integerList[256]", "1");
binder.bind(pvs);
- assertThat(tb.getIntegerList().size()).isEqualTo(257);
+ assertThat(tb.getIntegerList()).hasSize(257);
assertThat(tb.getIntegerList().get(256)).isEqualTo(Integer.valueOf(1));
assertThat(binder.getBindingResult().getFieldValue("integerList[256]")).isEqualTo(1);
}
diff --git a/spring-context/src/test/java/org/springframework/validation/ValidationUtilsTests.java b/spring-context/src/test/java/org/springframework/validation/ValidationUtilsTests.java
index 0a027b95df43..1131a2d645c6 100644
--- a/spring-context/src/test/java/org/springframework/validation/ValidationUtilsTests.java
+++ b/spring-context/src/test/java/org/springframework/validation/ValidationUtilsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@
public class ValidationUtilsTests {
@Test
- public void testInvokeValidatorWithNullValidator() throws Exception {
+ public void testInvokeValidatorWithNullValidator() {
TestBean tb = new TestBean();
Errors errors = new BeanPropertyBindingResult(tb, "tb");
assertThatIllegalArgumentException().isThrownBy(() ->
@@ -43,14 +43,14 @@ public void testInvokeValidatorWithNullValidator() throws Exception {
}
@Test
- public void testInvokeValidatorWithNullErrors() throws Exception {
+ public void testInvokeValidatorWithNullErrors() {
TestBean tb = new TestBean();
assertThatIllegalArgumentException().isThrownBy(() ->
ValidationUtils.invokeValidator(new EmptyValidator(), tb, null));
}
@Test
- public void testInvokeValidatorSunnyDay() throws Exception {
+ public void testInvokeValidatorSunnyDay() {
TestBean tb = new TestBean();
Errors errors = new BeanPropertyBindingResult(tb, "tb");
ValidationUtils.invokeValidator(new EmptyValidator(), tb, errors);
@@ -59,7 +59,7 @@ public void testInvokeValidatorSunnyDay() throws Exception {
}
@Test
- public void testValidationUtilsSunnyDay() throws Exception {
+ public void testValidationUtilsSunnyDay() {
TestBean tb = new TestBean("");
Validator testValidator = new EmptyValidator();
@@ -75,7 +75,7 @@ public void testValidationUtilsSunnyDay() throws Exception {
}
@Test
- public void testValidationUtilsNull() throws Exception {
+ public void testValidationUtilsNull() {
TestBean tb = new TestBean();
Errors errors = new BeanPropertyBindingResult(tb, "tb");
Validator testValidator = new EmptyValidator();
@@ -85,7 +85,7 @@ public void testValidationUtilsNull() throws Exception {
}
@Test
- public void testValidationUtilsEmpty() throws Exception {
+ public void testValidationUtilsEmpty() {
TestBean tb = new TestBean("");
Errors errors = new BeanPropertyBindingResult(tb, "tb");
Validator testValidator = new EmptyValidator();
@@ -113,7 +113,7 @@ public void testValidationUtilsEmptyVariants() {
}
@Test
- public void testValidationUtilsEmptyOrWhitespace() throws Exception {
+ public void testValidationUtilsEmptyOrWhitespace() {
TestBean tb = new TestBean();
Validator testValidator = new EmptyOrWhitespaceValidator();
diff --git a/spring-core/src/main/java/org/springframework/core/MethodIntrospector.java b/spring-core/src/main/java/org/springframework/core/MethodIntrospector.java
index 2947945cc488..9f905cbda473 100644
--- a/spring-core/src/main/java/org/springframework/core/MethodIntrospector.java
+++ b/spring-core/src/main/java/org/springframework/core/MethodIntrospector.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -74,7 +74,8 @@ public static Map selectMethods(Class> targetType, final Metada
T result = metadataLookup.inspect(specificMethod);
if (result != null) {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
- if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
+ if (bridgedMethod == specificMethod || bridgedMethod == method ||
+ metadataLookup.inspect(bridgedMethod) == null) {
methodMap.put(specificMethod, result);
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/ParameterizedTypeReference.java b/spring-core/src/main/java/org/springframework/core/ParameterizedTypeReference.java
index c10dd89aee9e..8e7bd2809f43 100644
--- a/spring-core/src/main/java/org/springframework/core/ParameterizedTypeReference.java
+++ b/spring-core/src/main/java/org/springframework/core/ParameterizedTypeReference.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-
package org.springframework.core;
import java.lang.reflect.ParameterizedType;
@@ -92,8 +91,7 @@ public String toString() {
* @since 4.3.12
*/
public static ParameterizedTypeReference forType(Type type) {
- return new ParameterizedTypeReference(type) {
- };
+ return new ParameterizedTypeReference(type) {};
}
private static Class> findParameterizedTypeReferenceSubclass(Class> child) {
diff --git a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java
index 9e56b5d5e37d..28a1152b5876 100644
--- a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java
+++ b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,8 +24,6 @@
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
-import kotlinx.coroutines.CompletableDeferredKt;
-import kotlinx.coroutines.Deferred;
import org.reactivestreams.Publisher;
import reactor.blockhound.BlockHound;
import reactor.blockhound.integration.BlockHoundIntegration;
@@ -39,13 +37,14 @@
import org.springframework.util.ReflectionUtils;
/**
- * A registry of adapters to adapt Reactive Streams {@link Publisher} to/from
- * various async/reactive types such as {@code CompletableFuture}, RxJava
- * {@code Flowable}, and others.
+ * A registry of adapters to adapt Reactive Streams {@link Publisher} to/from various
+ * async/reactive types such as {@code CompletableFuture}, RxJava {@code Flowable}, etc.
+ * This is designed to complement Spring's Reactor {@code Mono}/{@code Flux} support while
+ * also being usable without Reactor, e.g. just for {@code org.reactivestreams} bridging.
*
- * By default, depending on classpath availability, adapters are registered
- * for Reactor, RxJava 3, {@link CompletableFuture}, {@code Flow.Publisher},
- * and Kotlin Coroutines' {@code Deferred} and {@code Flow}.
+ *
By default, depending on classpath availability, adapters are registered for Reactor
+ * (including {@code CompletableFuture} and {@code Flow.Publisher} adapters), RxJava 3,
+ * Kotlin Coroutines' {@code Deferred} (bridged via Reactor) and SmallRye Mutiny 1.x.
*
*
Note: As of Spring Framework 5.3.11, support for
* RxJava 1.x and 2.x is deprecated in favor of RxJava 3.
@@ -136,16 +135,45 @@ public boolean hasAdapters() {
* Register a reactive type along with functions to adapt to and from a
* Reactive Streams {@link Publisher}. The function arguments assume that
* their input is neither {@code null} nor {@link Optional}.
+ *
This variant registers the new adapter after existing adapters.
+ * It will be matched for the exact reactive type if no earlier adapter was
+ * registered for the specific type, and it will be matched for assignability
+ * in a second pass if no earlier adapter had an assignable type before.
+ * @see #registerReactiveTypeOverride
+ * @see #getAdapter
*/
public void registerReactiveType(ReactiveTypeDescriptor descriptor,
Function> toAdapter, Function, Object> fromAdapter) {
- if (reactorPresent) {
- this.adapters.add(new ReactorAdapter(descriptor, toAdapter, fromAdapter));
- }
- else {
- this.adapters.add(new ReactiveAdapter(descriptor, toAdapter, fromAdapter));
- }
+ this.adapters.add(buildAdapter(descriptor, toAdapter, fromAdapter));
+ }
+
+ /**
+ * Register a reactive type along with functions to adapt to and from a
+ * Reactive Streams {@link Publisher}. The function arguments assume that
+ * their input is neither {@code null} nor {@link Optional}.
+ * This variant registers the new adapter first, effectively overriding
+ * any previously registered adapters for the same reactive type. This allows
+ * for overriding existing adapters, in particular default adapters.
+ *
Note that existing adapters for specific types will still match before
+ * an assignability match with the new adapter. In order to override all
+ * existing matches, a new reactive type adapter needs to be registered
+ * for every specific type, not relying on subtype assignability matches.
+ * @since 5.3.30
+ * @see #registerReactiveType
+ * @see #getAdapter
+ */
+ public void registerReactiveTypeOverride(ReactiveTypeDescriptor descriptor,
+ Function> toAdapter, Function, Object> fromAdapter) {
+
+ this.adapters.add(0, buildAdapter(descriptor, toAdapter, fromAdapter));
+ }
+
+ private ReactiveAdapter buildAdapter(ReactiveTypeDescriptor descriptor,
+ Function> toAdapter, Function, Object> fromAdapter) {
+
+ return (reactorPresent ? new ReactorAdapter(descriptor, toAdapter, fromAdapter) :
+ new ReactiveAdapter(descriptor, toAdapter, fromAdapter));
}
/**
@@ -401,9 +429,9 @@ private static class CoroutinesRegistrar {
@SuppressWarnings("KotlinInternalInJava")
void registerAdapters(ReactiveAdapterRegistry registry) {
registry.registerReactiveType(
- ReactiveTypeDescriptor.singleOptionalValue(Deferred.class,
- () -> CompletableDeferredKt.CompletableDeferred(null)),
- source -> CoroutinesUtils.deferredToMono((Deferred>) source),
+ ReactiveTypeDescriptor.singleOptionalValue(kotlinx.coroutines.Deferred.class,
+ () -> kotlinx.coroutines.CompletableDeferredKt.CompletableDeferred(null)),
+ source -> CoroutinesUtils.deferredToMono((kotlinx.coroutines.Deferred>) source),
source -> CoroutinesUtils.monoToDeferred(Mono.from(source)));
registry.registerReactiveType(
diff --git a/spring-core/src/main/java/org/springframework/core/ReactiveTypeDescriptor.java b/spring-core/src/main/java/org/springframework/core/ReactiveTypeDescriptor.java
index f1d76c330e24..5e37afabf097 100644
--- a/spring-core/src/main/java/org/springframework/core/ReactiveTypeDescriptor.java
+++ b/spring-core/src/main/java/org/springframework/core/ReactiveTypeDescriptor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,7 +37,7 @@ public final class ReactiveTypeDescriptor {
private final boolean noValue;
@Nullable
- private final Supplier> emptyValueSupplier;
+ private final Supplier> emptySupplier;
private final boolean deferred;
@@ -55,7 +55,7 @@ private ReactiveTypeDescriptor(Class> reactiveType, boolean multiValue, boolea
this.reactiveType = reactiveType;
this.multiValue = multiValue;
this.noValue = noValue;
- this.emptyValueSupplier = emptySupplier;
+ this.emptySupplier = emptySupplier;
this.deferred = deferred;
}
@@ -89,16 +89,16 @@ public boolean isNoValue() {
* Return {@code true} if the reactive type can complete with no values.
*/
public boolean supportsEmpty() {
- return (this.emptyValueSupplier != null);
+ return (this.emptySupplier != null);
}
/**
* Return an empty-value instance for the underlying reactive or async type.
- * Use of this type implies {@link #supportsEmpty()} is {@code true}.
+ * Use of this type implies {@link #supportsEmpty()} is {@code true}.
*/
public Object getEmptyValue() {
- Assert.state(this.emptyValueSupplier != null, "Empty values not supported");
- return this.emptyValueSupplier.get();
+ Assert.state(this.emptySupplier != null, "Empty values not supported");
+ return this.emptySupplier.get();
}
/**
diff --git a/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java b/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java
index 8a4643ea6475..bbd1399d5a7d 100644
--- a/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java
+++ b/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
index dd51fd45e447..a18c0c3212a8 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
@@ -1321,6 +1321,9 @@ public static boolean isSynthesizedAnnotation(@Nullable Annotation annotation) {
public static void clearCache() {
AnnotationTypeMappings.clearCache();
AnnotationsScanner.clearCache();
+ AttributeMethods.cache.clear();
+ RepeatableContainers.cache.clear();
+ OrderUtils.orderCache.clear();
}
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java b/spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java
index dc122981aa0d..a828ebe44b5a 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,9 +39,7 @@ final class AttributeMethods {
static final AttributeMethods NONE = new AttributeMethods(null, new Method[0]);
-
- private static final Map, AttributeMethods> cache =
- new ConcurrentReferenceHashMap<>();
+ static final Map, AttributeMethods> cache = new ConcurrentReferenceHashMap<>();
private static final Comparator methodComparator = (m1, m2) -> {
if (m1 != null && m2 != null) {
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/OrderUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/OrderUtils.java
index 25acbc848161..62a135842b7a 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/OrderUtils.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/OrderUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,7 +41,7 @@ public abstract class OrderUtils {
private static final String JAVAX_PRIORITY_ANNOTATION = "javax.annotation.Priority";
/** Cache for @Order value (or NOT_ANNOTATED marker) per Class. */
- private static final Map orderCache = new ConcurrentReferenceHashMap<>(64);
+ static final Map orderCache = new ConcurrentReferenceHashMap<>(64);
/**
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java b/spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java
index e163f7d92928..8460018c5bd6 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java
@@ -43,6 +43,8 @@
*/
public abstract class RepeatableContainers {
+ static final Map, Object> cache = new ConcurrentReferenceHashMap<>();
+
@Nullable
private final RepeatableContainers parent;
@@ -137,8 +139,6 @@ public static RepeatableContainers none() {
*/
private static class StandardRepeatableContainers extends RepeatableContainers {
- private static final Map, Object> cache = new ConcurrentReferenceHashMap<>();
-
private static final Object NONE = new Object();
private static StandardRepeatableContainers INSTANCE = new StandardRepeatableContainers();
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java b/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java
index 6065d4685247..5cff48af2101 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -205,8 +205,7 @@ public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceTy
* @param targetType the target type
* @return the converted value
* @throws ConversionException if a conversion exception occurred
- * @throws IllegalArgumentException if targetType is {@code null},
- * or sourceType is {@code null} but source is not {@code null}
+ * @throws IllegalArgumentException if targetType is {@code null}
*/
@Nullable
public Object convert(@Nullable Object source, TypeDescriptor targetType) {
diff --git a/spring-core/src/main/java/org/springframework/core/env/CommandLinePropertySource.java b/spring-core/src/main/java/org/springframework/core/env/CommandLinePropertySource.java
index 0f80080d1d6b..c317f5e2dc72 100644
--- a/spring-core/src/main/java/org/springframework/core/env/CommandLinePropertySource.java
+++ b/spring-core/src/main/java/org/springframework/core/env/CommandLinePropertySource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -252,7 +252,7 @@ public void setNonOptionArgsPropertyName(String nonOptionArgsPropertyName) {
@Override
public final boolean containsProperty(String name) {
if (this.nonOptionArgsPropertyName.equals(name)) {
- return !this.getNonOptionArgs().isEmpty();
+ return !getNonOptionArgs().isEmpty();
}
return this.containsOption(name);
}
@@ -270,7 +270,7 @@ public final boolean containsProperty(String name) {
@Nullable
public final String getProperty(String name) {
if (this.nonOptionArgsPropertyName.equals(name)) {
- Collection nonOptionArguments = this.getNonOptionArgs();
+ Collection nonOptionArguments = getNonOptionArgs();
if (nonOptionArguments.isEmpty()) {
return null;
}
@@ -278,7 +278,7 @@ public final String getProperty(String name) {
return StringUtils.collectionToCommaDelimitedString(nonOptionArguments);
}
}
- Collection optionValues = this.getOptionValues(name);
+ Collection optionValues = getOptionValues(name);
if (optionValues == null) {
return null;
}
diff --git a/spring-core/src/main/java/org/springframework/core/env/ProfilesParser.java b/spring-core/src/main/java/org/springframework/core/env/ProfilesParser.java
index 5e7fce7c6c41..9b666f91ed61 100644
--- a/spring-core/src/main/java/org/springframework/core/env/ProfilesParser.java
+++ b/spring-core/src/main/java/org/springframework/core/env/ProfilesParser.java
@@ -168,31 +168,27 @@ public boolean matches(Predicate activeProfiles) {
return false;
}
- @Override
- public int hashCode() {
- return this.expressions.hashCode();
- }
-
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
+ if (obj == null || getClass() != obj.getClass()) {
return false;
}
ParsedProfiles that = (ParsedProfiles) obj;
return this.expressions.equals(that.expressions);
}
+ @Override
+ public int hashCode() {
+ return this.expressions.hashCode();
+ }
+
@Override
public String toString() {
return StringUtils.collectionToDelimitedString(this.expressions, " or ");
}
-
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java b/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java
index 072502a868c9..8540697a52b3 100644
--- a/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java
+++ b/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author 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,8 +33,8 @@
* {@link TaskExecutor} implementation that fires up a new Thread for each task,
* executing it asynchronously.
*
- * Supports limiting concurrent threads through the "concurrencyLimit"
- * bean property. By default, the number of concurrent threads is unlimited.
+ *
Supports limiting concurrent threads through {@link #setConcurrencyLimit}.
+ * By default, the number of concurrent task executions is unlimited.
*
*
NOTE: This implementation does not reuse threads! Consider a
* thread-pooling TaskExecutor implementation instead, in particular for
@@ -133,33 +133,31 @@ public final ThreadFactory getThreadFactory() {
* have to cast it and call {@code Future#get} to evaluate exceptions.
* @since 4.3
*/
- public final void setTaskDecorator(TaskDecorator taskDecorator) {
+ public void setTaskDecorator(TaskDecorator taskDecorator) {
this.taskDecorator = taskDecorator;
}
/**
- * Set the maximum number of parallel accesses allowed.
- * -1 indicates no concurrency limit at all.
- *
In principle, this limit can be changed at runtime,
- * although it is generally designed as a config time setting.
- * NOTE: Do not switch between -1 and any concrete limit at runtime,
- * as this will lead to inconsistent concurrency counts: A limit
- * of -1 effectively turns off concurrency counting completely.
+ * Set the maximum number of parallel task executions allowed.
+ * The default of -1 indicates no concurrency limit at all.
+ *
This is the equivalent of a maximum pool size in a thread pool,
+ * preventing temporary overload of the thread management system.
* @see #UNBOUNDED_CONCURRENCY
+ * @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor#setMaxPoolSize
*/
public void setConcurrencyLimit(int concurrencyLimit) {
this.concurrencyThrottle.setConcurrencyLimit(concurrencyLimit);
}
/**
- * Return the maximum number of parallel accesses allowed.
+ * Return the maximum number of parallel task executions allowed.
*/
public final int getConcurrencyLimit() {
return this.concurrencyThrottle.getConcurrencyLimit();
}
/**
- * Return whether this throttle is currently active.
+ * Return whether the concurrency throttle is currently active.
* @return {@code true} if the concurrency limit for this instance is active
* @see #getConcurrencyLimit()
* @see #setConcurrencyLimit
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataReadingVisitor.java
index a951194ae7dc..700fcc5138c1 100644
--- a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataReadingVisitor.java
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataReadingVisitor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -171,6 +171,7 @@ private boolean isInterface(int access) {
return (access & Opcodes.ACC_INTERFACE) != 0;
}
+
/**
* {@link MergedAnnotation} source.
*/
@@ -182,11 +183,6 @@ private static final class Source {
this.className = className;
}
- @Override
- public int hashCode() {
- return this.className.hashCode();
- }
-
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
@@ -198,11 +194,15 @@ public boolean equals(@Nullable Object obj) {
return this.className.equals(((Source) obj).className);
}
+ @Override
+ public int hashCode() {
+ return this.className.hashCode();
+ }
+
@Override
public String toString() {
return this.className;
}
-
}
}
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 d5858c7399c7..4a84db6733b7 100644
--- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java
@@ -50,6 +50,7 @@
* @author Keith Donald
* @author Rob Harrop
* @author Sam Brannen
+ * @author Sebastien Deleuze
* @since 1.1
* @see TypeUtils
* @see ReflectionUtils
@@ -83,6 +84,12 @@ public abstract class ClassUtils {
/** The ".class" file suffix. */
public static final String CLASS_FILE_SUFFIX = ".class";
+ /** Precomputed value for the combination of private, static and final modifiers. */
+ private static final int NON_OVERRIDABLE_MODIFIER = Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL;
+
+ /** Precomputed value for the combination of public and protected modifiers. */
+ private static final int OVERRIDABLE_MODIFIER = Modifier.PUBLIC | Modifier.PROTECTED;
+
/**
* Map with primitive wrapper type as key and corresponding primitive
@@ -1379,10 +1386,10 @@ private static boolean isGroovyObjectMethod(Method method) {
* @param targetClass the target class to check against
*/
private static boolean isOverridable(Method method, @Nullable Class> targetClass) {
- if (Modifier.isPrivate(method.getModifiers())) {
+ if ((method.getModifiers() & NON_OVERRIDABLE_MODIFIER) != 0) {
return false;
}
- if (Modifier.isPublic(method.getModifiers()) || Modifier.isProtected(method.getModifiers())) {
+ if ((method.getModifiers() & OVERRIDABLE_MODIFIER) != 0) {
return true;
}
return (targetClass == null ||
@@ -1403,7 +1410,7 @@ public static Method getStaticMethod(Class> clazz, String methodName, Class>
Assert.notNull(methodName, "Method name must not be null");
try {
Method method = clazz.getMethod(methodName, args);
- return Modifier.isStatic(method.getModifiers()) ? method : null;
+ return (Modifier.isStatic(method.getModifiers()) ? method : null);
}
catch (NoSuchMethodException ex) {
return null;
diff --git a/spring-core/src/main/java/org/springframework/util/ConcurrencyThrottleSupport.java b/spring-core/src/main/java/org/springframework/util/ConcurrencyThrottleSupport.java
index 6d7d272b4fda..370537bf9a38 100644
--- a/spring-core/src/main/java/org/springframework/util/ConcurrencyThrottleSupport.java
+++ b/spring-core/src/main/java/org/springframework/util/ConcurrencyThrottleSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -69,7 +69,7 @@ public abstract class ConcurrencyThrottleSupport implements Serializable {
/**
* Set the maximum number of concurrent access attempts allowed.
- * -1 indicates unbounded concurrency.
+ * The default of -1 indicates no concurrency limit at all.
*
In principle, this limit can be changed at runtime,
* although it is generally designed as a config time setting.
*
NOTE: Do not switch between -1 and any concrete limit at runtime,
@@ -143,9 +143,10 @@ protected void beforeAccess() {
*/
protected void afterAccess() {
if (this.concurrencyLimit >= 0) {
+ boolean debug = logger.isDebugEnabled();
synchronized (this.monitor) {
this.concurrencyCount--;
- if (logger.isDebugEnabled()) {
+ if (debug) {
logger.debug("Returning from throttle at concurrency count " + this.concurrencyCount);
}
this.monitor.notify();
diff --git a/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java b/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java
index 3291ca6e079a..75507d411d49 100644
--- a/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java
+++ b/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -752,28 +752,27 @@ public V setValue(@Nullable V value) {
}
@Override
- public String toString() {
- return (this.key + "=" + this.value);
- }
-
- @Override
- @SuppressWarnings("rawtypes")
- public final boolean equals(@Nullable Object other) {
+ public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
- if (!(other instanceof Map.Entry)) {
+ if (!(other instanceof Map.Entry, ?>)) {
return false;
}
- Map.Entry otherEntry = (Map.Entry) other;
+ Map.Entry, ?> otherEntry = (Map.Entry, ?>) other;
return (ObjectUtils.nullSafeEquals(getKey(), otherEntry.getKey()) &&
ObjectUtils.nullSafeEquals(getValue(), otherEntry.getValue()));
}
@Override
- public final int hashCode() {
+ public int hashCode() {
return (ObjectUtils.nullSafeHashCode(this.key) ^ ObjectUtils.nullSafeHashCode(this.value));
}
+
+ @Override
+ public String toString() {
+ return (this.key + "=" + this.value);
+ }
}
diff --git a/spring-core/src/main/java/org/springframework/util/MethodInvoker.java b/spring-core/src/main/java/org/springframework/util/MethodInvoker.java
index b3e0c5554806..0443c5699ca1 100644
--- a/spring-core/src/main/java/org/springframework/util/MethodInvoker.java
+++ b/spring-core/src/main/java/org/springframework/util/MethodInvoker.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -123,8 +123,8 @@ public String getTargetMethod() {
/**
* Set a fully qualified static method name to invoke,
- * e.g. "example.MyExampleClass.myExampleMethod".
- * Convenient alternative to specifying targetClass and targetMethod.
+ * e.g. "example.MyExampleClass.myExampleMethod". This is a
+ * convenient alternative to specifying targetClass and targetMethod.
* @see #setTargetClass
* @see #setTargetMethod
*/
@@ -157,14 +157,16 @@ public Object[] getArguments() {
public void prepare() throws ClassNotFoundException, NoSuchMethodException {
if (this.staticMethod != null) {
int lastDotIndex = this.staticMethod.lastIndexOf('.');
- if (lastDotIndex == -1 || lastDotIndex == this.staticMethod.length()) {
+ if (lastDotIndex == -1 || lastDotIndex == this.staticMethod.length() - 1) {
throw new IllegalArgumentException(
"staticMethod must be a fully qualified class plus method name: " +
"e.g. 'example.MyExampleClass.myExampleMethod'");
}
String className = this.staticMethod.substring(0, lastDotIndex);
String methodName = this.staticMethod.substring(lastDotIndex + 1);
- this.targetClass = resolveClassName(className);
+ if (this.targetClass == null || !this.targetClass.getName().equals(className)) {
+ this.targetClass = resolveClassName(className);
+ }
this.targetMethod = methodName;
}
diff --git a/spring-core/src/main/java/org/springframework/util/ObjectUtils.java b/spring-core/src/main/java/org/springframework/util/ObjectUtils.java
index 4475152488e1..64aef0d25a7a 100644
--- a/spring-core/src/main/java/org/springframework/util/ObjectUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/ObjectUtils.java
@@ -70,8 +70,8 @@ public abstract class ObjectUtils {
private static final String ARRAY_ELEMENT_SEPARATOR = ", ";
private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
private static final String NON_EMPTY_ARRAY = ARRAY_START + "..." + ARRAY_END;
- private static final String EMPTY_COLLECTION = "[]";
- private static final String NON_EMPTY_COLLECTION = "[...]";
+ private static final String COLLECTION = "[...]";
+ private static final String MAP = NON_EMPTY_ARRAY;
/**
@@ -938,10 +938,9 @@ public static String nullSafeToString(@Nullable short[] array) {
*
{@code"Optional[]"} if {@code obj} is a non-empty {@code Optional},
* where {@code } is the result of invoking {@link #nullSafeConciseToString}
* on the object contained in the {@code Optional}
- * {@code "{}"} if {@code obj} is an empty array or {@link Map}
- * {@code "{...}"} if {@code obj} is a non-empty array or {@link Map}
- * {@code "[]"} if {@code obj} is an empty {@link Collection}
- * {@code "[...]"} if {@code obj} is a non-empty {@link Collection}
+ * {@code "{}"} if {@code obj} is an empty array
+ * {@code "{...}"} if {@code obj} is a {@link Map} or a non-empty array
+ * {@code "[...]"} if {@code obj} is a {@link Collection}
* {@linkplain Class#getName() Class name} if {@code obj} is a {@link Class}
* {@linkplain Charset#name() Charset name} if {@code obj} is a {@link Charset}
* {@linkplain TimeZone#getID() TimeZone ID} if {@code obj} is a {@link TimeZone}
@@ -977,12 +976,11 @@ public static String nullSafeConciseToString(@Nullable Object obj) {
if (obj.getClass().isArray()) {
return (Array.getLength(obj) == 0 ? EMPTY_ARRAY : NON_EMPTY_ARRAY);
}
- if (obj instanceof Collection>) {
- return (((Collection>) obj).isEmpty() ? EMPTY_COLLECTION : NON_EMPTY_COLLECTION);
+ if (obj instanceof Collection) {
+ return COLLECTION;
}
- if (obj instanceof Map, ?>) {
- // EMPTY_ARRAY and NON_EMPTY_ARRAY are also used for maps.
- return (((Map, ?>) obj).isEmpty() ? EMPTY_ARRAY : NON_EMPTY_ARRAY);
+ if (obj instanceof Map) {
+ return MAP;
}
if (obj instanceof Class>) {
return ((Class>) obj).getName();
diff --git a/spring-core/src/main/java/org/springframework/util/StringUtils.java b/spring-core/src/main/java/org/springframework/util/StringUtils.java
index e803a05db2d2..96290a3cea61 100644
--- a/spring-core/src/main/java/org/springframework/util/StringUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/StringUtils.java
@@ -247,26 +247,26 @@ public static String trimWhitespace(String str) {
/**
* Trim all whitespace from the given {@code CharSequence}:
* leading, trailing, and in between characters.
- * @param text the {@code CharSequence} to check
+ * @param str the {@code CharSequence} to check
* @return the trimmed {@code CharSequence}
* @since 5.3.22
* @see #trimAllWhitespace(String)
* @see java.lang.Character#isWhitespace
*/
- public static CharSequence trimAllWhitespace(CharSequence text) {
- if (!hasLength(text)) {
- return text;
+ public static CharSequence trimAllWhitespace(CharSequence str) {
+ if (!hasLength(str)) {
+ return str;
}
- int len = text.length();
- StringBuilder sb = new StringBuilder(text.length());
+ int len = str.length();
+ StringBuilder sb = new StringBuilder(str.length());
for (int i = 0; i < len; i++) {
- char c = text.charAt(i);
+ char c = str.charAt(i);
if (!Character.isWhitespace(c)) {
sb.append(c);
}
}
- return sb.toString();
+ return sb;
}
/**
@@ -278,9 +278,10 @@ public static CharSequence trimAllWhitespace(CharSequence text) {
* @see java.lang.Character#isWhitespace
*/
public static String trimAllWhitespace(String str) {
- if (str == null) {
- return null;
+ if (!hasLength(str)) {
+ return str;
}
+
return trimAllWhitespace((CharSequence) str).toString();
}
diff --git a/spring-core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java b/spring-core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java
index 37933af5397d..6eae4de69b55 100644
--- a/spring-core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java
+++ b/spring-core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -85,7 +85,7 @@ private NullSafeComparator(boolean nullsLow) {
* @param nullsLow whether to treat nulls lower or higher than non-null objects
*/
public NullSafeComparator(Comparator comparator, boolean nullsLow) {
- Assert.notNull(comparator, "Non-null Comparator is required");
+ Assert.notNull(comparator, "Comparator must not be null");
this.nonNullComparator = comparator;
this.nullsLow = nullsLow;
}
@@ -107,16 +107,16 @@ public int compare(@Nullable T o1, @Nullable T o2) {
@Override
- @SuppressWarnings("unchecked")
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
- if (!(other instanceof NullSafeComparator)) {
+ if (!(other instanceof NullSafeComparator>)) {
return false;
}
- NullSafeComparator otherComp = (NullSafeComparator) other;
- return (this.nonNullComparator.equals(otherComp.nonNullComparator) && this.nullsLow == otherComp.nullsLow);
+ NullSafeComparator> otherComp = (NullSafeComparator>) other;
+ return (this.nonNullComparator.equals(otherComp.nonNullComparator) &&
+ this.nullsLow == otherComp.nullsLow);
}
@Override
diff --git a/spring-core/src/test/java/org/springframework/core/ReactiveAdapterRegistryTests.java b/spring-core/src/test/java/org/springframework/core/ReactiveAdapterRegistryTests.java
index 35683ac0cbee..8d4728b2e69b 100644
--- a/spring-core/src/test/java/org/springframework/core/ReactiveAdapterRegistryTests.java
+++ b/spring-core/src/test/java/org/springframework/core/ReactiveAdapterRegistryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@
* Unit tests for {@link ReactiveAdapterRegistry}.
*
* @author Rossen Stoyanchev
+ * @author Juergen Hoeller
*/
@SuppressWarnings("unchecked")
class ReactiveAdapterRegistryTests {
@@ -52,14 +53,40 @@ void getAdapterForReactiveSubType() {
ReactiveAdapter adapter2 = getAdapter(ExtendedFlux.class);
assertThat(adapter2).isSameAs(adapter1);
+ // Register regular reactive type (after existing adapters)
this.registry.registerReactiveType(
ReactiveTypeDescriptor.multiValue(ExtendedFlux.class, ExtendedFlux::empty),
o -> (ExtendedFlux>) o,
ExtendedFlux::from);
+ // Matches for ExtendedFlux itself
ReactiveAdapter adapter3 = getAdapter(ExtendedFlux.class);
assertThat(adapter3).isNotNull();
assertThat(adapter3).isNotSameAs(adapter1);
+
+ // Does not match for ExtendedFlux subclass since the default Flux adapter
+ // is being assignability-checked first when no specific match was found
+ ReactiveAdapter adapter4 = getAdapter(ExtendedExtendedFlux.class);
+ assertThat(adapter4).isSameAs(adapter1);
+
+ // Register reactive type override (before existing adapters)
+ this.registry.registerReactiveTypeOverride(
+ ReactiveTypeDescriptor.multiValue(Flux.class, ExtendedFlux::empty),
+ o -> (ExtendedFlux>) o,
+ ExtendedFlux::from);
+
+ // Override match for Flux
+ ReactiveAdapter adapter5 = getAdapter(Flux.class);
+ assertThat(adapter5).isNotNull();
+ assertThat(adapter5).isNotSameAs(adapter1);
+
+ // Initially registered adapter specifically matches for ExtendedFlux
+ ReactiveAdapter adapter6 = getAdapter(ExtendedFlux.class);
+ assertThat(adapter6).isSameAs(adapter3);
+
+ // Override match for ExtendedFlux subclass
+ ReactiveAdapter adapter7 = getAdapter(ExtendedExtendedFlux.class);
+ assertThat(adapter7).isSameAs(adapter5);
}
@@ -79,6 +106,10 @@ public void subscribe(CoreSubscriber super T> actual) {
}
+ private static class ExtendedExtendedFlux extends ExtendedFlux {
+ }
+
+
@Nested
class Reactor {
@@ -100,7 +131,7 @@ void toFlux() {
List sequence = Arrays.asList(1, 2, 3);
Publisher source = io.reactivex.rxjava3.core.Flowable.fromIterable(sequence);
Object target = getAdapter(Flux.class).fromPublisher(source);
- assertThat(target instanceof Flux).isTrue();
+ assertThat(target).isInstanceOf(Flux.class);
assertThat(((Flux) target).collectList().block(ONE_SECOND)).isEqualTo(sequence);
}
@@ -108,7 +139,7 @@ void toFlux() {
void toMono() {
Publisher source = io.reactivex.rxjava3.core.Flowable.fromArray(1, 2, 3);
Object target = getAdapter(Mono.class).fromPublisher(source);
- assertThat(target instanceof Mono).isTrue();
+ assertThat(target).isInstanceOf(Mono.class);
assertThat(((Mono) target).block(ONE_SECOND)).isEqualTo(Integer.valueOf(1));
}
@@ -116,7 +147,7 @@ void toMono() {
void toCompletableFuture() throws Exception {
Publisher source = Flux.fromArray(new Integer[] {1, 2, 3});
Object target = getAdapter(CompletableFuture.class).fromPublisher(source);
- assertThat(target instanceof CompletableFuture).isTrue();
+ assertThat(target).isInstanceOf(CompletableFuture.class);
assertThat(((CompletableFuture) target).get()).isEqualTo(Integer.valueOf(1));
}
@@ -125,7 +156,7 @@ void fromCompletableFuture() {
CompletableFuture future = new CompletableFuture<>();
future.complete(1);
Object target = getAdapter(CompletableFuture.class).toPublisher(future);
- assertThat(target instanceof Mono).as("Expected Mono Publisher: " + target.getClass().getName()).isTrue();
+ assertThat(target).as("Expected Mono Publisher: " + target.getClass().getName()).isInstanceOf(Mono.class);
assertThat(((Mono) target).block(ONE_SECOND)).isEqualTo(Integer.valueOf(1));
}
}
@@ -294,7 +325,7 @@ void toFlowable() {
List sequence = Arrays.asList(1, 2, 3);
Publisher source = Flux.fromIterable(sequence);
Object target = getAdapter(io.reactivex.rxjava3.core.Flowable.class).fromPublisher(source);
- assertThat(target instanceof io.reactivex.rxjava3.core.Flowable).isTrue();
+ assertThat(target).isInstanceOf(io.reactivex.rxjava3.core.Flowable.class);
assertThat(((io.reactivex.rxjava3.core.Flowable>) target).toList().blockingGet()).isEqualTo(sequence);
}
@@ -303,7 +334,7 @@ void toObservable() {
List sequence = Arrays.asList(1, 2, 3);
Publisher source = Flux.fromIterable(sequence);
Object target = getAdapter(io.reactivex.rxjava3.core.Observable.class).fromPublisher(source);
- assertThat(target instanceof io.reactivex.rxjava3.core.Observable).isTrue();
+ assertThat(target).isInstanceOf(io.reactivex.rxjava3.core.Observable.class);
assertThat(((io.reactivex.rxjava3.core.Observable>) target).toList().blockingGet()).isEqualTo(sequence);
}
@@ -311,7 +342,7 @@ void toObservable() {
void toSingle() {
Publisher source = Flux.fromArray(new Integer[] {1});
Object target = getAdapter(io.reactivex.rxjava3.core.Single.class).fromPublisher(source);
- assertThat(target instanceof io.reactivex.rxjava3.core.Single).isTrue();
+ assertThat(target).isInstanceOf(io.reactivex.rxjava3.core.Single.class);
assertThat(((io.reactivex.rxjava3.core.Single) target).blockingGet()).isEqualTo(Integer.valueOf(1));
}
@@ -319,7 +350,7 @@ void toSingle() {
void toCompletable() {
Publisher source = Flux.fromArray(new Integer[] {1, 2, 3});
Object target = getAdapter(io.reactivex.rxjava3.core.Completable.class).fromPublisher(source);
- assertThat(target instanceof io.reactivex.rxjava3.core.Completable).isTrue();
+ assertThat(target).isInstanceOf(io.reactivex.rxjava3.core.Completable.class);
((io.reactivex.rxjava3.core.Completable) target).blockingAwait();
}
@@ -328,7 +359,7 @@ void fromFlowable() {
List sequence = Arrays.asList(1, 2, 3);
Object source = io.reactivex.rxjava3.core.Flowable.fromIterable(sequence);
Object target = getAdapter(io.reactivex.rxjava3.core.Flowable.class).toPublisher(source);
- assertThat(target instanceof Flux).as("Expected Flux Publisher: " + target.getClass().getName()).isTrue();
+ assertThat(target).as("Expected Flux Publisher: " + target.getClass().getName()).isInstanceOf(Flux.class);
assertThat(((Flux) target).collectList().block(ONE_SECOND)).isEqualTo(sequence);
}
@@ -337,7 +368,7 @@ void fromObservable() {
List sequence = Arrays.asList(1, 2, 3);
Object source = io.reactivex.rxjava3.core.Observable.fromIterable(sequence);
Object target = getAdapter(io.reactivex.rxjava3.core.Observable.class).toPublisher(source);
- assertThat(target instanceof Flux).as("Expected Flux Publisher: " + target.getClass().getName()).isTrue();
+ assertThat(target).as("Expected Flux Publisher: " + target.getClass().getName()).isInstanceOf(Flux.class);
assertThat(((Flux) target).collectList().block(ONE_SECOND)).isEqualTo(sequence);
}
@@ -345,7 +376,7 @@ void fromObservable() {
void fromSingle() {
Object source = io.reactivex.rxjava3.core.Single.just(1);
Object target = getAdapter(io.reactivex.rxjava3.core.Single.class).toPublisher(source);
- assertThat(target instanceof Mono).as("Expected Mono Publisher: " + target.getClass().getName()).isTrue();
+ assertThat(target).as("Expected Mono Publisher: " + target.getClass().getName()).isInstanceOf(Mono.class);
assertThat(((Mono) target).block(ONE_SECOND)).isEqualTo(Integer.valueOf(1));
}
@@ -353,7 +384,7 @@ void fromSingle() {
void fromCompletable() {
Object source = io.reactivex.rxjava3.core.Completable.complete();
Object target = getAdapter(io.reactivex.rxjava3.core.Completable.class).toPublisher(source);
- assertThat(target instanceof Mono).as("Expected Mono Publisher: " + target.getClass().getName()).isTrue();
+ assertThat(target).as("Expected Mono Publisher: " + target.getClass().getName()).isInstanceOf(Mono.class);
((Mono) target).block(ONE_SECOND);
}
}
diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/ByteBufferConverterTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/ByteBufferConverterTests.java
index 9c0f46ecb438..c1bcf11551b9 100644
--- a/spring-core/src/test/java/org/springframework/core/convert/support/ByteBufferConverterTests.java
+++ b/spring-core/src/test/java/org/springframework/core/convert/support/ByteBufferConverterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2023 the original author 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,57 +33,56 @@
*/
class ByteBufferConverterTests {
- private GenericConversionService conversionService;
+ private final GenericConversionService conversionService = new DefaultConversionService();
@BeforeEach
void setup() {
- this.conversionService = new DefaultConversionService();
- this.conversionService.addConverter(new ByteArrayToOtherTypeConverter());
- this.conversionService.addConverter(new OtherTypeToByteArrayConverter());
+ conversionService.addConverter(new ByteArrayToOtherTypeConverter());
+ conversionService.addConverter(new OtherTypeToByteArrayConverter());
}
@Test
- void byteArrayToByteBuffer() throws Exception {
+ void byteArrayToByteBuffer() {
byte[] bytes = new byte[] { 1, 2, 3 };
- ByteBuffer convert = this.conversionService.convert(bytes, ByteBuffer.class);
+ ByteBuffer convert = conversionService.convert(bytes, ByteBuffer.class);
assertThat(convert.array()).isNotSameAs(bytes);
assertThat(convert.array()).isEqualTo(bytes);
}
@Test
- void byteBufferToByteArray() throws Exception {
+ void byteBufferToByteArray() {
byte[] bytes = new byte[] { 1, 2, 3 };
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
- byte[] convert = this.conversionService.convert(byteBuffer, byte[].class);
+ byte[] convert = conversionService.convert(byteBuffer, byte[].class);
assertThat(convert).isNotSameAs(bytes);
assertThat(convert).isEqualTo(bytes);
}
@Test
- void byteBufferToOtherType() throws Exception {
+ void byteBufferToOtherType() {
byte[] bytes = new byte[] { 1, 2, 3 };
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
- OtherType convert = this.conversionService.convert(byteBuffer, OtherType.class);
+ OtherType convert = conversionService.convert(byteBuffer, OtherType.class);
assertThat(convert.bytes).isNotSameAs(bytes);
assertThat(convert.bytes).isEqualTo(bytes);
}
@Test
- void otherTypeToByteBuffer() throws Exception {
+ void otherTypeToByteBuffer() {
byte[] bytes = new byte[] { 1, 2, 3 };
OtherType otherType = new OtherType(bytes);
- ByteBuffer convert = this.conversionService.convert(otherType, ByteBuffer.class);
+ ByteBuffer convert = conversionService.convert(otherType, ByteBuffer.class);
assertThat(convert.array()).isNotSameAs(bytes);
assertThat(convert.array()).isEqualTo(bytes);
}
@Test
- void byteBufferToByteBuffer() throws Exception {
+ void byteBufferToByteBuffer() {
byte[] bytes = new byte[] { 1, 2, 3 };
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
- ByteBuffer convert = this.conversionService.convert(byteBuffer, ByteBuffer.class);
+ ByteBuffer convert = conversionService.convert(byteBuffer, ByteBuffer.class);
assertThat(convert).isNotSameAs(byteBuffer.rewind());
assertThat(convert).isEqualTo(byteBuffer.rewind());
assertThat(convert).isEqualTo(ByteBuffer.wrap(bytes));
diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java
index b7eca2c60473..dfcb5e61a436 100644
--- a/spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java
+++ b/spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -49,11 +49,11 @@
*/
class CollectionToCollectionConverterTests {
- private GenericConversionService conversionService = new GenericConversionService();
+ private final GenericConversionService conversionService = new GenericConversionService();
@BeforeEach
- void setUp() {
+ void setup() {
conversionService.addConverter(new CollectionToCollectionConverter(conversionService));
}
diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java
index fd900a79e779..14542589901e 100644
--- a/spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java
+++ b/spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,7 +38,7 @@
/**
* @author Keith Donald
- * @author Phil Webb
+ * @author Phillip Webb
* @author Juergen Hoeller
*/
class MapToMapConverterTests {
@@ -47,7 +47,7 @@ class MapToMapConverterTests {
@BeforeEach
- void setUp() {
+ void setup() {
conversionService.addConverter(new MapToMapConverter(conversionService));
}
diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/ObjectToObjectConverterTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/ObjectToObjectConverterTests.java
index 7565d74992aa..4af44b77f4fa 100644
--- a/spring-core/src/test/java/org/springframework/core/convert/support/ObjectToObjectConverterTests.java
+++ b/spring-core/src/test/java/org/springframework/core/convert/support/ObjectToObjectConverterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
import java.util.Optional;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.convert.ConverterNotFoundException;
@@ -29,15 +30,19 @@
* Unit tests for {@link ObjectToObjectConverter}.
*
* @author Sam Brannen
- * @author Phil Webb
+ * @author Phillip Webb
* @since 5.3.21
* @see org.springframework.core.convert.converter.DefaultConversionServiceTests#convertObjectToObjectUsingValueOfMethod()
*/
class ObjectToObjectConverterTests {
- private final GenericConversionService conversionService = new GenericConversionService() {{
- addConverter(new ObjectToObjectConverter());
- }};
+ private final GenericConversionService conversionService = new GenericConversionService();
+
+
+ @BeforeEach
+ void setup() {
+ conversionService.addConverter(new ObjectToObjectConverter());
+ }
/**
@@ -47,7 +52,7 @@ class ObjectToObjectConverterTests {
@Test
void nonStaticToTargetTypeSimpleNameMethodWithMatchingReturnType() {
assertThat(conversionService.canConvert(Source.class, Data.class))
- .as("can convert Source to Data").isTrue();
+ .as("can convert Source to Data").isTrue();
Data data = conversionService.convert(new Source("test"), Data.class);
assertThat(data).asString().isEqualTo("test");
}
@@ -55,21 +60,21 @@ void nonStaticToTargetTypeSimpleNameMethodWithMatchingReturnType() {
@Test
void nonStaticToTargetTypeSimpleNameMethodWithDifferentReturnType() {
assertThat(conversionService.canConvert(Text.class, Data.class))
- .as("can convert Text to Data").isFalse();
+ .as("can convert Text to Data").isFalse();
assertThat(conversionService.canConvert(Text.class, Optional.class))
- .as("can convert Text to Optional").isFalse();
+ .as("can convert Text to Optional").isFalse();
assertThatExceptionOfType(ConverterNotFoundException.class)
- .as("convert Text to Data")
- .isThrownBy(() -> conversionService.convert(new Text("test"), Data.class));
+ .as("convert Text to Data")
+ .isThrownBy(() -> conversionService.convert(new Text("test"), Data.class));
}
@Test
void staticValueOfFactoryMethodWithDifferentReturnType() {
assertThat(conversionService.canConvert(String.class, Data.class))
- .as("can convert String to Data").isFalse();
+ .as("can convert String to Data").isFalse();
assertThatExceptionOfType(ConverterNotFoundException.class)
- .as("convert String to Data")
- .isThrownBy(() -> conversionService.convert("test", Data.class));
+ .as("convert String to Data")
+ .isThrownBy(() -> conversionService.convert("test", Data.class));
}
@@ -84,9 +89,9 @@ private Source(String value) {
public Data toData() {
return new Data(this.value);
}
-
}
+
static class Text {
private final String value;
@@ -98,9 +103,9 @@ private Text(String value) {
public Optional toData() {
return Optional.of(new Data(this.value));
}
-
}
+
static class Data {
private final String value;
@@ -115,9 +120,8 @@ public String toString() {
}
public static Optional valueOf(String string) {
- return (string != null) ? Optional.of(new Data(string)) : Optional.empty();
+ return (string != null ? Optional.of(new Data(string)) : Optional.empty());
}
-
}
}
diff --git a/spring-core/src/test/java/org/springframework/util/ObjectUtilsTests.java b/spring-core/src/test/java/org/springframework/util/ObjectUtilsTests.java
index a4d3b8cee39c..a17f0bd10e0f 100644
--- a/spring-core/src/test/java/org/springframework/util/ObjectUtilsTests.java
+++ b/spring-core/src/test/java/org/springframework/util/ObjectUtilsTests.java
@@ -1078,8 +1078,8 @@ void nullSafeConciseToStringForNonEmptyArrays() {
void nullSafeConciseToStringForEmptyCollections() {
List list = Collections.emptyList();
Set set = Collections.emptySet();
- assertThat(ObjectUtils.nullSafeConciseToString(list)).isEqualTo("[]");
- assertThat(ObjectUtils.nullSafeConciseToString(set)).isEqualTo("[]");
+ assertThat(ObjectUtils.nullSafeConciseToString(list)).isEqualTo("[...]");
+ assertThat(ObjectUtils.nullSafeConciseToString(set)).isEqualTo("[...]");
}
@Test
@@ -1094,7 +1094,7 @@ void nullSafeConciseToStringForNonEmptyCollections() {
@Test
void nullSafeConciseToStringForEmptyMaps() {
Map map = Collections.emptyMap();
- assertThat(ObjectUtils.nullSafeConciseToString(map)).isEqualTo("{}");
+ assertThat(ObjectUtils.nullSafeConciseToString(map)).isEqualTo("{...}");
}
@Test
diff --git a/spring-core/src/testFixtures/java/org/springframework/core/testfixture/env/MockPropertySource.java b/spring-core/src/testFixtures/java/org/springframework/core/testfixture/env/MockPropertySource.java
index ae4944ddccb7..a4cbd1f22ed1 100644
--- a/spring-core/src/testFixtures/java/org/springframework/core/testfixture/env/MockPropertySource.java
+++ b/spring-core/src/testFixtures/java/org/springframework/core/testfixture/env/MockPropertySource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
*
* The {@link #setProperty} and {@link #withProperty} methods are exposed for
* convenience, for example:
- *
+ *
* {@code
* PropertySource> source = new MockPropertySource().withProperty("foo", "bar");
* }
@@ -77,7 +77,7 @@ public MockPropertySource(Properties properties) {
/**
* Create a new {@code MockPropertySource} with the given name and backed by the given
- * {@link Properties} object
+ * {@link Properties} object.
* @param name the {@linkplain #getName() name} of the property source
* @param properties the properties to use
*/
@@ -99,7 +99,7 @@ public void setProperty(String name, Object value) {
* @return this {@link MockPropertySource} instance
*/
public MockPropertySource withProperty(String name, Object value) {
- this.setProperty(name, value);
+ setProperty(name, value);
return this;
}
diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java
index 54275a974e4f..cf135d08ed23 100644
--- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java
@@ -77,7 +77,6 @@
import org.springframework.expression.spel.ast.TypeReference;
import org.springframework.expression.spel.ast.VariableReference;
import org.springframework.lang.Nullable;
-import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@@ -137,12 +136,13 @@ protected SpelExpression doParseExpression(String expressionString, @Nullable Pa
this.tokenStreamPointer = 0;
this.constructedNodes.clear();
SpelNodeImpl ast = eatExpression();
- Assert.state(ast != null, "No node");
+ if (ast == null) {
+ throw new SpelParseException(this.expressionString, 0, SpelMessage.OOD);
+ }
Token t = peekToken();
if (t != null) {
- throw new SpelParseException(t.startPos, SpelMessage.MORE_INPUT, toString(nextToken()));
+ throw new SpelParseException(this.expressionString, t.startPos, SpelMessage.MORE_INPUT, toString(nextToken()));
}
- Assert.isTrue(this.constructedNodes.isEmpty(), "At least one node expected");
return new SpelExpression(expressionString, ast, this.configuration);
}
catch (InternalParseException ex) {
@@ -254,20 +254,20 @@ private SpelNodeImpl eatRelationalExpression() {
if (tk == TokenKind.EQ) {
return new OpEQ(t.startPos, t.endPos, expr, rhExpr);
}
- Assert.isTrue(tk == TokenKind.NE, "Not-equals token expected");
- return new OpNE(t.startPos, t.endPos, expr, rhExpr);
+ if (tk == TokenKind.NE) {
+ return new OpNE(t.startPos, t.endPos, expr, rhExpr);
+ }
}
if (tk == TokenKind.INSTANCEOF) {
return new OperatorInstanceof(t.startPos, t.endPos, expr, rhExpr);
}
-
if (tk == TokenKind.MATCHES) {
return new OperatorMatches(this.patternCache, t.startPos, t.endPos, expr, rhExpr);
}
-
- Assert.isTrue(tk == TokenKind.BETWEEN, "Between token expected");
- return new OperatorBetween(t.startPos, t.endPos, expr, rhExpr);
+ if (tk == TokenKind.BETWEEN) {
+ return new OperatorBetween(t.startPos, t.endPos, expr, rhExpr);
+ }
}
return expr;
}
@@ -304,8 +304,7 @@ private SpelNodeImpl eatProductExpression() {
else if (t.kind == TokenKind.DIV) {
expr = new OpDivide(t.startPos, t.endPos, expr, rhExpr);
}
- else {
- Assert.isTrue(t.kind == TokenKind.MOD, "Mod token expected");
+ else if (t.kind == TokenKind.MOD) {
expr = new OpModulus(t.startPos, t.endPos, expr, rhExpr);
}
}
@@ -335,18 +334,21 @@ private SpelNodeImpl eatPowerIncDecExpression() {
// unaryExpression: (PLUS^ | MINUS^ | BANG^ | INC^ | DEC^) unaryExpression | primaryExpression ;
@Nullable
private SpelNodeImpl eatUnaryExpression() {
- if (peekToken(TokenKind.PLUS, TokenKind.MINUS, TokenKind.NOT)) {
+ if (peekToken(TokenKind.NOT, TokenKind.PLUS, TokenKind.MINUS)) {
Token t = takeToken();
SpelNodeImpl expr = eatUnaryExpression();
- Assert.state(expr != null, "No node");
+ if (expr == null) {
+ throw internalException(t.startPos, SpelMessage.OOD);
+ }
if (t.kind == TokenKind.NOT) {
return new OperatorNot(t.startPos, t.endPos, expr);
}
if (t.kind == TokenKind.PLUS) {
return new OpPlus(t.startPos, t.endPos, expr);
}
- Assert.isTrue(t.kind == TokenKind.MINUS, "Minus token expected");
- return new OpMinus(t.startPos, t.endPos, expr);
+ if (t.kind == TokenKind.MINUS) {
+ return new OpMinus(t.startPos, t.endPos, expr);
+ }
}
if (peekToken(TokenKind.INC, TokenKind.DEC)) {
Token t = takeToken();
@@ -354,7 +356,9 @@ private SpelNodeImpl eatUnaryExpression() {
if (t.getKind() == TokenKind.INC) {
return new OpInc(t.startPos, t.endPos, false, expr);
}
- return new OpDec(t.startPos, t.endPos, false, expr);
+ if (t.kind == TokenKind.DEC) {
+ return new OpDec(t.startPos, t.endPos, false, expr);
+ }
}
return eatPrimaryExpression();
}
@@ -414,7 +418,6 @@ private SpelNodeImpl eatDottedNode() {
return pop();
}
if (peekToken() == null) {
- // unexpectedly ran out of data
throw internalException(t.startPos, SpelMessage.OOD);
}
else {
@@ -460,8 +463,7 @@ private SpelNodeImpl[] maybeEatMethodArgs() {
private void eatConstructorArgs(List accumulatedArguments) {
if (!peekToken(TokenKind.LPAREN)) {
- throw new InternalParseException(new SpelParseException(this.expressionString,
- positionOf(peekToken()), SpelMessage.MISSING_CONSTRUCTOR_ARGS));
+ throw internalException(positionOf(peekToken()), SpelMessage.MISSING_CONSTRUCTOR_ARGS);
}
consumeArguments(accumulatedArguments);
eatToken(TokenKind.RPAREN);
@@ -472,7 +474,9 @@ private void eatConstructorArgs(List accumulatedArguments) {
*/
private void consumeArguments(List accumulatedArguments) {
Token t = peekToken();
- Assert.state(t != null, "Expected token");
+ if (t == null) {
+ return;
+ }
int pos = t.startPos;
Token next;
do {
@@ -575,8 +579,7 @@ else if (peekToken(TokenKind.LITERAL_STRING)) {
private boolean maybeEatTypeReference() {
if (peekToken(TokenKind.IDENTIFIER)) {
Token typeName = peekToken();
- Assert.state(typeName != null, "Expected token");
- if (!"T".equals(typeName.stringValue())) {
+ if (typeName == null || !"T".equals(typeName.stringValue())) {
return false;
}
// It looks like a type reference but is T being used as a map key?
@@ -605,8 +608,7 @@ private boolean maybeEatTypeReference() {
private boolean maybeEatNullReference() {
if (peekToken(TokenKind.IDENTIFIER)) {
Token nullToken = peekToken();
- Assert.state(nullToken != null, "Expected token");
- if (!"null".equalsIgnoreCase(nullToken.stringValue())) {
+ if (nullToken == null || !"null".equalsIgnoreCase(nullToken.stringValue())) {
return false;
}
nextToken();
@@ -619,12 +621,13 @@ private boolean maybeEatNullReference() {
//projection: PROJECT^ expression RCURLY!;
private boolean maybeEatProjection(boolean nullSafeNavigation) {
Token t = peekToken();
- if (!peekToken(TokenKind.PROJECT, true)) {
+ if (t == null || !peekToken(TokenKind.PROJECT, true)) {
return false;
}
- Assert.state(t != null, "No token");
SpelNodeImpl expr = eatExpression();
- Assert.state(expr != null, "No node");
+ if (expr == null) {
+ throw internalException(t.startPos, SpelMessage.OOD);
+ }
eatToken(TokenKind.RSQUARE);
this.constructedNodes.push(new Projection(nullSafeNavigation, t.startPos, t.endPos, expr));
return true;
@@ -634,15 +637,13 @@ private boolean maybeEatProjection(boolean nullSafeNavigation) {
// map = LCURLY (key ':' value (COMMA key ':' value)*) RCURLY
private boolean maybeEatInlineListOrMap() {
Token t = peekToken();
- if (!peekToken(TokenKind.LCURLY, true)) {
+ if (t == null || !peekToken(TokenKind.LCURLY, true)) {
return false;
}
- Assert.state(t != null, "No token");
SpelNodeImpl expr = null;
Token closingCurly = peekToken();
- if (peekToken(TokenKind.RCURLY, true)) {
+ if (closingCurly != null && peekToken(TokenKind.RCURLY, true)) {
// empty list '{}'
- Assert.state(closingCurly != null, "No token");
expr = new InlineList(t.startPos, closingCurly.endPos);
}
else if (peekToken(TokenKind.COLON, true)) {
@@ -695,12 +696,13 @@ else if (peekToken(TokenKind.COLON, true)) { // map!
private boolean maybeEatIndexer() {
Token t = peekToken();
- if (!peekToken(TokenKind.LSQUARE, true)) {
+ if (t == null || !peekToken(TokenKind.LSQUARE, true)) {
return false;
}
- Assert.state(t != null, "No token");
SpelNodeImpl expr = eatExpression();
- Assert.state(expr != null, "No node");
+ if (expr == null) {
+ throw internalException(t.startPos, SpelMessage.MISSING_SELECTION_EXPRESSION);
+ }
eatToken(TokenKind.RSQUARE);
this.constructedNodes.push(new Indexer(t.startPos, t.endPos, expr));
return true;
@@ -708,10 +710,9 @@ private boolean maybeEatIndexer() {
private boolean maybeEatSelection(boolean nullSafeNavigation) {
Token t = peekToken();
- if (!peekSelectToken()) {
+ if (t == null || !peekSelectToken()) {
return false;
}
- Assert.state(t != null, "No token");
nextToken();
SpelNodeImpl expr = eatExpression();
if (expr == null) {
@@ -889,9 +890,14 @@ else if (t.kind == TokenKind.LITERAL_STRING) {
//parenExpr : LPAREN! expression RPAREN!;
private boolean maybeEatParenExpression() {
if (peekToken(TokenKind.LPAREN)) {
- nextToken();
+ Token t = nextToken();
+ if (t == null) {
+ return false;
+ }
SpelNodeImpl expr = eatExpression();
- Assert.state(expr != null, "No node");
+ if (expr == null) {
+ throw internalException(t.startPos, SpelMessage.OOD);
+ }
eatToken(TokenKind.RPAREN);
push(expr);
return true;
diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/Token.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/Token.java
index dda0ff2152c0..f00f26a30037 100644
--- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/Token.java
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/Token.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -65,7 +65,7 @@ public boolean isIdentifier() {
public boolean isNumericRelationalOperator() {
return (this.kind == TokenKind.GT || this.kind == TokenKind.GE || this.kind == TokenKind.LT ||
- this.kind == TokenKind.LE || this.kind==TokenKind.EQ || this.kind==TokenKind.NE);
+ this.kind == TokenKind.LE || this.kind == TokenKind.EQ || this.kind == TokenKind.NE);
}
public String stringValue() {
@@ -87,14 +87,14 @@ public Token asBetweenToken() {
@Override
public String toString() {
- StringBuilder s = new StringBuilder();
- s.append('[').append(this.kind.toString());
+ StringBuilder sb = new StringBuilder();
+ sb.append('[').append(this.kind);
if (this.kind.hasPayload()) {
- s.append(':').append(this.data);
+ sb.append(':').append(this.data);
}
- s.append(']');
- s.append('(').append(this.startPos).append(',').append(this.endPos).append(')');
- return s.toString();
+ sb.append(']');
+ sb.append('(').append(this.startPos).append(',').append(this.endPos).append(')');
+ return sb.toString();
}
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java
index 33b6e7298e23..bccc0a5b8c2b 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java
@@ -39,7 +39,9 @@
import static org.springframework.expression.spel.SpelMessage.NON_TERMINATING_QUOTED_STRING;
import static org.springframework.expression.spel.SpelMessage.NOT_AN_INTEGER;
import static org.springframework.expression.spel.SpelMessage.NOT_A_LONG;
+import static org.springframework.expression.spel.SpelMessage.OOD;
import static org.springframework.expression.spel.SpelMessage.REAL_CANNOT_BE_LONG;
+import static org.springframework.expression.spel.SpelMessage.RIGHT_OPERAND_PROBLEM;
import static org.springframework.expression.spel.SpelMessage.RUN_OUT_OF_ARGUMENTS;
import static org.springframework.expression.spel.SpelMessage.UNEXPECTED_DATA_AFTER_DOT;
import static org.springframework.expression.spel.SpelMessage.UNEXPECTED_ESCAPE_CHAR;
@@ -76,8 +78,8 @@ void blankExpressionIsRejected() {
private static void assertNullOrEmptyExpressionIsRejected(ThrowingCallable throwingCallable) {
assertThatIllegalArgumentException()
- .isThrownBy(throwingCallable)
- .withMessage("'expressionString' must not be null or blank");
+ .isThrownBy(throwingCallable)
+ .withMessage("'expressionString' must not be null or blank");
}
@Test
@@ -152,7 +154,13 @@ void parseExceptions() {
assertParseException(() -> parser.parseRaw("new String(3"), RUN_OUT_OF_ARGUMENTS, 10);
assertParseException(() -> parser.parseRaw("new String("), RUN_OUT_OF_ARGUMENTS, 10);
assertParseException(() -> parser.parseRaw("\"abc"), NON_TERMINATING_DOUBLE_QUOTED_STRING, 0);
+ assertParseException(() -> parser.parseRaw("abc\""), NON_TERMINATING_DOUBLE_QUOTED_STRING, 3);
assertParseException(() -> parser.parseRaw("'abc"), NON_TERMINATING_QUOTED_STRING, 0);
+ assertParseException(() -> parser.parseRaw("abc'"), NON_TERMINATING_QUOTED_STRING, 3);
+ assertParseException(() -> parser.parseRaw("("), OOD, 0);
+ assertParseException(() -> parser.parseRaw(")"), OOD, 0);
+ assertParseException(() -> parser.parseRaw("+"), OOD, 0);
+ assertParseException(() -> parser.parseRaw("1+"), RIGHT_OPERAND_PROBLEM, 1);
}
@Test
@@ -377,7 +385,7 @@ private void checkNumber(String expression, Object value, Class> type) {
private void checkNumberError(String expression, SpelMessage expectedMessage) {
assertParseExceptionThrownBy(() -> parser.parseRaw(expression))
- .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(expectedMessage));
+ .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(expectedMessage));
}
private static ThrowableAssertAlternative assertParseExceptionThrownBy(ThrowingCallable throwingCallable) {
@@ -386,15 +394,18 @@ private static ThrowableAssertAlternative assertParseExcepti
private static void assertParseException(ThrowingCallable throwingCallable, SpelMessage expectedMessage, int expectedPosition) {
assertParseExceptionThrownBy(throwingCallable)
- .satisfies(parseExceptionRequirements(expectedMessage, expectedPosition));
+ .satisfies(parseExceptionRequirements(expectedMessage, expectedPosition));
}
private static Consumer parseExceptionRequirements(
SpelMessage expectedMessage, int expectedPosition) {
+
return ex -> {
assertThat(ex.getMessageCode()).isEqualTo(expectedMessage);
assertThat(ex.getPosition()).isEqualTo(expectedPosition);
- assertThat(ex.getMessage()).contains(ex.getExpressionString());
+ if (ex.getExpressionString() != null) {
+ assertThat(ex.getMessage()).contains(ex.getExpressionString());
+ }
};
}
diff --git a/spring-jcl/src/main/java/org/apache/commons/logging/LogFactory.java b/spring-jcl/src/main/java/org/apache/commons/logging/LogFactory.java
index e67eb5a4342a..34426cc33f48 100644
--- a/spring-jcl/src/main/java/org/apache/commons/logging/LogFactory.java
+++ b/spring-jcl/src/main/java/org/apache/commons/logging/LogFactory.java
@@ -77,7 +77,25 @@ public static Log getLog(String name) {
*/
@Deprecated
public static LogFactory getFactory() {
- return new LogFactory() {};
+ return new LogFactory() {
+ @Override
+ public Object getAttribute(String name) {
+ return null;
+ }
+ @Override
+ public String[] getAttributeNames() {
+ return new String[0];
+ }
+ @Override
+ public void removeAttribute(String name) {
+ }
+ @Override
+ public void setAttribute(String name, Object value) {
+ }
+ @Override
+ public void release() {
+ }
+ };
}
/**
@@ -106,29 +124,19 @@ public Log getInstance(String name) {
// Just in case some code happens to call uncommon Commons Logging methods...
@Deprecated
- public Object getAttribute(String name) {
- return null;
- }
+ public abstract Object getAttribute(String name);
@Deprecated
- public String[] getAttributeNames() {
- return new String[0];
- }
+ public abstract String[] getAttributeNames();
@Deprecated
- public void removeAttribute(String name) {
- // do nothing
- }
+ public abstract void removeAttribute(String name);
@Deprecated
- public void setAttribute(String name, Object value) {
- // do nothing
- }
+ public abstract void setAttribute(String name, Object value);
@Deprecated
- public void release() {
- // do nothing
- }
+ public abstract void release();
@Deprecated
public static void release(ClassLoader classLoader) {
diff --git a/spring-jcl/src/main/java/org/apache/commons/logging/LogFactoryService.java b/spring-jcl/src/main/java/org/apache/commons/logging/LogFactoryService.java
index bf2379b5693e..c40a928a0272 100644
--- a/spring-jcl/src/main/java/org/apache/commons/logging/LogFactoryService.java
+++ b/spring-jcl/src/main/java/org/apache/commons/logging/LogFactoryService.java
@@ -74,4 +74,8 @@ public String[] getAttributeNames() {
return this.attributes.keySet().toArray(new String[0]);
}
+ @Override
+ public void release() {
+ }
+
}
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java
index cd51ba286baa..47d4f4955f98 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java
@@ -343,7 +343,7 @@ public T mapRow(ResultSet rs, int rowNumber) throws SQLException {
bw.setPropertyValue(pd.getName(), value);
}
catch (TypeMismatchException ex) {
- if (value == null && this.primitivesDefaultedForNullValue) {
+ if (value == null && isPrimitivesDefaultedForNullValue()) {
if (logger.isDebugEnabled()) {
String propertyType = ClassUtils.getQualifiedName(pd.getPropertyType());
logger.debug(String.format(
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java
index 75937f348bbf..7af681f52b4d 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author 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,12 +35,13 @@
* Alternatively, the standard JDBC infrastructure can be mocked.
* However, mocking this interface constitutes significantly less work.
* As an alternative to a mock objects approach to testing data access code,
- * consider the powerful integration testing support provided via the Spring
- * TestContext Framework , in the {@code spring-test} artifact.
+ * consider the powerful integration testing support provided via the
+ * Spring TestContext Framework , in the {@code spring-test} artifact.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see JdbcTemplate
+ * @see org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations
*/
public interface JdbcOperations {
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
index 225ab2b12235..335a96022a6a 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
@@ -65,8 +65,7 @@
* It executes core JDBC workflow, leaving application code to provide SQL
* and extract results. This class executes SQL queries or updates, initiating
* iteration over ResultSets and catching JDBC exceptions and translating
- * them to the generic, more informative exception hierarchy defined in the
- * {@code org.springframework.dao} package.
+ * them to the common {@code org.springframework.dao} exception hierarchy.
*
*
Code using this class need only implement callback interfaces, giving
* them a clearly defined contract. The {@link PreparedStatementCreator} callback
@@ -75,7 +74,8 @@
* values from a ResultSet. See also {@link PreparedStatementSetter} and
* {@link RowMapper} for two popular alternative callback interfaces.
*
- *
Can be used within a service implementation via direct instantiation
+ *
An instance of this template class is thread-safe once configured.
+ * Can be used within a service implementation via direct instantiation
* with a DataSource reference, or get prepared in an application context
* and given to services as bean reference. Note: The DataSource should
* always be configured as a bean in the application context, in the first case
@@ -88,12 +88,11 @@
*
All SQL operations performed by this class are logged at debug level,
* using "org.springframework.jdbc.core.JdbcTemplate" as log category.
*
- *
NOTE: An instance of this class is thread-safe once configured.
- *
* @author Rod Johnson
* @author Juergen Hoeller
* @author Thomas Risberg
* @since May 3, 2001
+ * @see JdbcOperations
* @see PreparedStatementCreator
* @see PreparedStatementSetter
* @see CallableStatementCreator
@@ -103,6 +102,7 @@
* @see RowCallbackHandler
* @see RowMapper
* @see org.springframework.jdbc.support.SQLExceptionTranslator
+ * @see org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
*/
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
@@ -445,9 +445,7 @@ public T query(final String sql, final ResultSetExtractor rse) throws Dat
logger.debug("Executing SQL query [" + sql + "]");
}
- /**
- * Callback to execute the query.
- */
+ // Callback to execute the query.
class QueryStatementCallback implements StatementCallback, SqlProvider {
@Override
@Nullable
@@ -542,9 +540,7 @@ public int update(final String sql) throws DataAccessException {
logger.debug("Executing SQL update [" + sql + "]");
}
- /**
- * Callback to execute the update statement.
- */
+ // Callback to execute the update statement.
class UpdateStatementCallback implements StatementCallback, SqlProvider {
@Override
public Integer doInStatement(Statement stmt) throws SQLException {
@@ -570,9 +566,7 @@ public int[] batchUpdate(final String... sql) throws DataAccessException {
logger.debug("Executing SQL batch update of " + sql.length + " statements");
}
- /**
- * Callback to execute the batch update.
- */
+ // Callback to execute the batch update.
class BatchUpdateStatementCallback implements StatementCallback, SqlProvider {
@Nullable
@@ -1376,7 +1370,7 @@ protected Map extractOutputParameters(CallableStatement cs, List
}
}
}
- if (!(param.isResultsParameter())) {
+ if (!param.isResultsParameter()) {
sqlColIndex++;
}
}
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/BeanPropertySqlParameterSource.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/BeanPropertySqlParameterSource.java
index 83b8edb24b90..be0233d47ce8 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/BeanPropertySqlParameterSource.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/BeanPropertySqlParameterSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,9 +32,10 @@
/**
* {@link SqlParameterSource} implementation that obtains parameter values
* from bean properties of a given JavaBean object. The names of the bean
- * properties have to match the parameter names.
+ * properties have to match the parameter names. Supports components of
+ * record classes as well, with accessor methods matching parameter names.
*
- * Uses a Spring BeanWrapper for bean property access underneath.
+ *
Uses a Spring {@link BeanWrapper} for bean property access underneath.
*
* @author Thomas Risberg
* @author Juergen Hoeller
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java
index 3d3752b5bf59..3c4043544711 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -55,16 +55,19 @@
* done at execution time. It also allows for expanding a {@link java.util.List}
* of values to the appropriate number of placeholders.
*
- *
The underlying {@link org.springframework.jdbc.core.JdbcTemplate} is
+ *
An instance of this template class is thread-safe once configured.
+ * The underlying {@link org.springframework.jdbc.core.JdbcTemplate} is
* exposed to allow for convenient access to the traditional
* {@link org.springframework.jdbc.core.JdbcTemplate} methods.
*
- *
NOTE: An instance of this class is thread-safe once configured.
- *
* @author Thomas Risberg
* @author Juergen Hoeller
* @since 2.0
* @see NamedParameterJdbcOperations
+ * @see SqlParameterSource
+ * @see ResultSetExtractor
+ * @see RowCallbackHandler
+ * @see RowMapper
* @see org.springframework.jdbc.core.JdbcTemplate
*/
public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations {
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcCall.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcCall.java
index 82a2ad19c13d..0121db51c0b2 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcCall.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcCall.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,7 +43,7 @@
* Abstract class to provide base functionality for easy stored procedure calls
* based on configuration options and database meta-data.
*
- *
This class provides the base SPI for {@link SimpleJdbcCall}.
+ *
This class provides the processing arrangement for {@link SimpleJdbcCall}.
*
* @author Thomas Risberg
* @author Juergen Hoeller
@@ -453,7 +453,7 @@ protected Map matchInParameterValuesWithCallParameters(SqlParame
/**
* Match the provided in parameter values with registered parameters and
* parameters defined via meta-data processing.
- * @param args the parameter values provided in a Map
+ * @param args the parameter values provided as a Map
* @return a Map with parameter names and values
*/
protected Map matchInParameterValuesWithCallParameters(Map args) {
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java
index 5d6d83c142d4..504ee7013b7e 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author 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,10 +50,10 @@
import org.springframework.util.Assert;
/**
- * Abstract class to provide base functionality for easy inserts
+ * Abstract class to provide base functionality for easy (batch) inserts
* based on configuration options and database meta-data.
*
- * This class provides the base SPI for {@link SimpleJdbcInsert}.
+ *
This class provides the processing arrangement for {@link SimpleJdbcInsert}.
*
* @author Thomas Risberg
* @author Juergen Hoeller
@@ -409,7 +409,7 @@ protected KeyHolder doExecuteAndReturnKeyHolder(SqlParameterSource parameterSour
/**
* Delegate method to execute the insert, generating a single key.
*/
- private Number executeInsertAndReturnKeyInternal(final List> values) {
+ private Number executeInsertAndReturnKeyInternal(List> values) {
KeyHolder kh = executeInsertAndReturnKeyHolderInternal(values);
if (kh.getKey() != null) {
return kh.getKey();
@@ -423,11 +423,11 @@ private Number executeInsertAndReturnKeyInternal(final List> values) {
/**
* Delegate method to execute the insert, generating any number of keys.
*/
- private KeyHolder executeInsertAndReturnKeyHolderInternal(final List> values) {
+ private KeyHolder executeInsertAndReturnKeyHolderInternal(List> values) {
if (logger.isDebugEnabled()) {
logger.debug("The following parameters are used for call " + getInsertString() + " with: " + values);
}
- final KeyHolder keyHolder = new GeneratedKeyHolder();
+ KeyHolder keyHolder = new GeneratedKeyHolder();
if (this.tableMetaDataContext.isGetGeneratedKeysSupported()) {
getJdbcTemplate().update(
@@ -455,7 +455,7 @@ private KeyHolder executeInsertAndReturnKeyHolderInternal(final List> values)
}
Assert.state(getTableName() != null, "No table name set");
- final String keyQuery = this.tableMetaDataContext.getSimpleQueryForGetGeneratedKey(
+ String keyQuery = this.tableMetaDataContext.getSimpleQueryForGetGeneratedKey(
getTableName(), getGeneratedKeyNames()[0]);
Assert.state(keyQuery != null, "Query for simulating get generated keys must not be null");
@@ -535,8 +535,8 @@ private PreparedStatement prepareStatementForGeneratedKeys(Connection con) throw
/**
* Delegate method that executes a batch insert using the passed-in Maps of parameters.
- * @param batch array of Maps with parameter names and values to be used in batch insert
- * @return array of number of rows affected
+ * @param batch maps with parameter names and values to be used in the batch insert
+ * @return an array of number of rows affected
*/
@SuppressWarnings("unchecked")
protected int[] doExecuteBatch(Map... batch) {
@@ -549,9 +549,10 @@ protected int[] doExecuteBatch(Map... batch) {
}
/**
- * Delegate method that executes a batch insert using the passed-in {@link SqlParameterSource SqlParameterSources}.
- * @param batch array of SqlParameterSource with parameter names and values to be used in insert
- * @return array of number of rows affected
+ * Delegate method that executes a batch insert using the passed-in
+ * {@link SqlParameterSource SqlParameterSources}.
+ * @param batch parameter sources with names and values to be used in the batch insert
+ * @return an array of number of rows affected
*/
protected int[] doExecuteBatch(SqlParameterSource... batch) {
checkCompiled();
@@ -606,7 +607,7 @@ private void setParameterValues(PreparedStatement preparedStatement, List> val
* Match the provided in parameter values with registered parameters and parameters
* defined via meta-data processing.
* @param parameterSource the parameter values provided as a {@link SqlParameterSource}
- * @return a Map with parameter names and values
+ * @return a List of values
*/
protected List matchInParameterValuesWithInsertColumns(SqlParameterSource parameterSource) {
return this.tableMetaDataContext.matchInParameterValuesWithInsertColumns(parameterSource);
@@ -615,8 +616,8 @@ protected List matchInParameterValuesWithInsertColumns(SqlParameterSourc
/**
* Match the provided in parameter values with registered parameters and parameters
* defined via meta-data processing.
- * @param args the parameter values provided in a Map
- * @return a Map with parameter names and values
+ * @param args the parameter values provided as a Map
+ * @return a List of values
*/
protected List matchInParameterValuesWithInsertColumns(Map args) {
return this.tableMetaDataContext.matchInParameterValuesWithInsertColumns(args);
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcCall.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcCall.java
index f26ea8aee3df..c5d946fad431 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcCall.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcCall.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2023 the original author 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 @@
* any meta-data processing if you want to use parameter names that do not
* match what is declared during the stored procedure compilation.
*
- * The actual insert is being handled using Spring's {@link JdbcTemplate}.
+ *
The actual call is being handled using Spring's {@link JdbcTemplate}.
*
*
Many of the configuration methods return the current instance of the
* SimpleJdbcCall in order to provide the ability to chain multiple ones
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsert.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsert.java
index ce210b39b43b..f48fb62d14d7 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsert.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsert.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author 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,17 +26,17 @@
import org.springframework.jdbc.support.KeyHolder;
/**
- * A SimpleJdbcInsert is a multithreaded, reusable object providing easy insert
+ * A SimpleJdbcInsert is a multithreaded, reusable object providing easy (batch) insert
* capabilities for a table. It provides meta-data processing to simplify the code
- * needed to construct a basic insert statement. All you need to provide is the
- * name of the table and a Map containing the column names and the column values.
+ * needed to construct a basic insert statement. All you need to provide is the name
+ * of the table and a Map containing the column names and the column values.
*
*
The meta-data processing is based on the DatabaseMetaData provided by the
* JDBC driver. As long as the JDBC driver can provide the names of the columns
* for a specified table then we can rely on this auto-detection feature. If that
* is not the case, then the column names must be specified explicitly.
*
- *
The actual insert is handled using Spring's {@link JdbcTemplate}.
+ *
The actual (batch) insert is handled using Spring's {@link JdbcTemplate}.
*
*
Many of the configuration methods return the current instance of the
* SimpleJdbcInsert to provide the ability to chain multiple ones together
@@ -142,8 +142,8 @@ public KeyHolder executeAndReturnKeyHolder(SqlParameterSource parameterSource) {
return doExecuteAndReturnKeyHolder(parameterSource);
}
- @Override
@SuppressWarnings("unchecked")
+ @Override
public int[] executeBatch(Map... batch) {
return doExecuteBatch(batch);
}
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/AbstractFallbackSQLExceptionTranslator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/AbstractFallbackSQLExceptionTranslator.java
index 44b8838b2101..f1546734ac16 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/AbstractFallbackSQLExceptionTranslator.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/AbstractFallbackSQLExceptionTranslator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author 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,8 @@
*
* @author Juergen Hoeller
* @since 2.5.6
+ * @see #doTranslate
+ * @see #setFallbackTranslator
*/
public abstract class AbstractFallbackSQLExceptionTranslator implements SQLExceptionTranslator {
@@ -42,8 +44,8 @@ public abstract class AbstractFallbackSQLExceptionTranslator implements SQLExcep
/**
- * Override the default SQL state fallback translator
- * (typically a {@link SQLStateSQLExceptionTranslator}).
+ * Set the fallback translator to use when this translator cannot find a
+ * specific match itself.
*/
public void setFallbackTranslator(@Nullable SQLExceptionTranslator fallback) {
this.fallbackTranslator = fallback;
@@ -51,6 +53,7 @@ public void setFallbackTranslator(@Nullable SQLExceptionTranslator fallback) {
/**
* Return the fallback exception translator, if any.
+ * @see #setFallbackTranslator
*/
@Nullable
public SQLExceptionTranslator getFallbackTranslator() {
diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslator.java
index 80f89796049d..c4ba08dbfec3 100644
--- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslator.java
+++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -75,8 +75,6 @@ public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExcep
private static final int MESSAGE_SQL_THROWABLE_CONSTRUCTOR = 4;
private static final int MESSAGE_SQL_SQLEX_CONSTRUCTOR = 5;
-
- /** Error codes used by this translator. */
@Nullable
private SingletonSupplier sqlErrorCodes;
@@ -194,9 +192,9 @@ protected DataAccessException doTranslate(String task, @Nullable String sql, SQL
if (sqlErrorCodes != null) {
SQLExceptionTranslator customTranslator = sqlErrorCodes.getCustomSqlExceptionTranslator();
if (customTranslator != null) {
- DataAccessException customDex = customTranslator.translate(task, sql, sqlEx);
- if (customDex != null) {
- return customDex;
+ dae = customTranslator.translate(task, sql, sqlEx);
+ if (dae != null) {
+ return dae;
}
}
}
@@ -224,11 +222,10 @@ protected DataAccessException doTranslate(String task, @Nullable String sql, SQL
for (CustomSQLErrorCodesTranslation customTranslation : customTranslations) {
if (Arrays.binarySearch(customTranslation.getErrorCodes(), errorCode) >= 0 &&
customTranslation.getExceptionClass() != null) {
- DataAccessException customException = createCustomException(
- task, sql, sqlEx, customTranslation.getExceptionClass());
- if (customException != null) {
+ dae = createCustomException(task, sql, sqlEx, customTranslation.getExceptionClass());
+ if (dae != null) {
logTranslation(task, sql, sqlEx, true);
- return customException;
+ return dae;
}
}
}
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 4fc63340bda8..b83219be06f2 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
@@ -50,7 +50,7 @@ class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
void overridingDifferentClassDefinedForMapping() {
BeanPropertyRowMapper mapper = new BeanPropertyRowMapper(Person.class);
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
- .isThrownBy(() -> mapper.setMappedClass(Long.class));
+ .isThrownBy(() -> mapper.setMappedClass(Long.class));
}
@Test
@@ -104,7 +104,7 @@ void mappingWithUnpopulatedFieldsNotAccepted() throws Exception {
BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(ExtendedPerson.class, true);
Mock mock = new Mock();
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
- .isThrownBy(() -> mock.getJdbcTemplate().query("select name, age, birth_date, balance from people", mapper));
+ .isThrownBy(() -> mock.getJdbcTemplate().query("select name, age, birth_date, balance from people", mapper));
}
@Test
@@ -112,7 +112,7 @@ void mappingNullValue() throws Exception {
BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(Person.class);
Mock mock = new Mock(MockType.TWO);
assertThatExceptionOfType(TypeMismatchException.class)
- .isThrownBy(() -> mock.getJdbcTemplate().query(SELECT_NULL_AS_AGE, mapper));
+ .isThrownBy(() -> mock.getJdbcTemplate().query(SELECT_NULL_AS_AGE, mapper));
}
@Test
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterQueryTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterQueryTests.java
index 1a2c61469908..8c47dcda143e 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterQueryTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterQueryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2023 the original author 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,8 +34,6 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.springframework.jdbc.core.RowMapper;
-
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
@@ -135,8 +133,7 @@ public void testQueryForListWithParamMapAndSingleRowAndColumn() throws Exception
}
@Test
- public void testQueryForListWithParamMapAndIntegerElementAndSingleRowAndColumn()
- throws Exception {
+ public void testQueryForListWithParamMapAndIntegerElementAndSingleRowAndColumn() throws Exception {
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getInt(1)).willReturn(11);
@@ -174,11 +171,10 @@ public void testQueryForObjectWithParamMapAndRowMapper() throws Exception {
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("id", 3);
- Object o = template.queryForObject("SELECT AGE FROM CUSTMR WHERE ID = :id",
- params, (RowMapper) (rs, rowNum) -> rs.getInt(1));
+ Integer value = template.queryForObject("SELECT AGE FROM CUSTMR WHERE ID = :id",
+ params, (rs, rowNum) -> rs.getInt(1));
- boolean condition = o instanceof Integer;
- assertThat(condition).as("Correct result type").isTrue();
+ assertThat(value).isEqualTo(22);
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
verify(preparedStatement).setObject(1, 3);
}
@@ -191,11 +187,10 @@ public void testQueryForObjectWithMapAndInteger() throws Exception {
Map params = new HashMap<>();
params.put("id", 3);
- Object o = template.queryForObject("SELECT AGE FROM CUSTMR WHERE ID = :id",
+ Integer value = template.queryForObject("SELECT AGE FROM CUSTMR WHERE ID = :id",
params, Integer.class);
- boolean condition = o instanceof Integer;
- assertThat(condition).as("Correct result type").isTrue();
+ assertThat(value).isEqualTo(22);
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
verify(preparedStatement).setObject(1, 3);
}
@@ -208,30 +203,26 @@ public void testQueryForObjectWithParamMapAndInteger() throws Exception {
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("id", 3);
- Object o = template.queryForObject("SELECT AGE FROM CUSTMR WHERE ID = :id",
+ Integer value = template.queryForObject("SELECT AGE FROM CUSTMR WHERE ID = :id",
params, Integer.class);
- boolean condition = o instanceof Integer;
- assertThat(condition).as("Correct result type").isTrue();
+ assertThat(value).isEqualTo(22);
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
verify(preparedStatement).setObject(1, 3);
}
@Test
public void testQueryForObjectWithParamMapAndList() throws Exception {
- String sql = "SELECT AGE FROM CUSTMR WHERE ID IN (:ids)";
- String sqlToUse = "SELECT AGE FROM CUSTMR WHERE ID IN (?, ?)";
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getInt(1)).willReturn(22);
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("ids", Arrays.asList(3, 4));
- Object o = template.queryForObject(sql, params, Integer.class);
+ Integer value = template.queryForObject("SELECT AGE FROM CUSTMR WHERE ID IN (:ids)", params, Integer.class);
- boolean condition = o instanceof Integer;
- assertThat(condition).as("Correct result type").isTrue();
- verify(connection).prepareStatement(sqlToUse);
+ assertThat(value).isEqualTo(22);
+ verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID IN (?, ?)");
verify(preparedStatement).setObject(1, 3);
}
@@ -246,14 +237,11 @@ public void testQueryForObjectWithParamMapAndListOfExpressionLists() throws Exce
l1.add(new Object[] {3, "Rod"});
l1.add(new Object[] {4, "Juergen"});
params.addValue("multiExpressionList", l1);
- Object o = template.queryForObject(
- "SELECT AGE FROM CUSTMR WHERE (ID, NAME) IN (:multiExpressionList)",
+ Integer value = template.queryForObject("SELECT AGE FROM CUSTMR WHERE (ID, NAME) IN (:multiExpressionList)",
params, Integer.class);
- boolean condition = o instanceof Integer;
- assertThat(condition).as("Correct result type").isTrue();
- verify(connection).prepareStatement(
- "SELECT AGE FROM CUSTMR WHERE (ID, NAME) IN ((?, ?), (?, ?))");
+ assertThat(value).isEqualTo(22);
+ verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE (ID, NAME) IN ((?, ?), (?, ?))");
verify(preparedStatement).setObject(1, 3);
}
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/support/LobSupportTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/support/LobSupportTests.java
index d0ba6edf8f9c..da07e652edb7 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/support/LobSupportTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/support/LobSupportTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,6 @@
import org.junit.jupiter.api.Test;
-import org.springframework.dao.DataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.LobRetrievalFailureException;
import org.springframework.jdbc.support.lob.LobCreator;
@@ -55,11 +54,9 @@ class SetValuesCalled {
final SetValuesCalled svc = new SetValuesCalled();
- AbstractLobCreatingPreparedStatementCallback psc = new AbstractLobCreatingPreparedStatementCallback(
- handler) {
+ AbstractLobCreatingPreparedStatementCallback psc = new AbstractLobCreatingPreparedStatementCallback(handler) {
@Override
- protected void setValues(PreparedStatement ps, LobCreator lobCreator)
- throws SQLException, DataAccessException {
+ protected void setValues(PreparedStatement ps, LobCreator lobCreator) {
svc.b = true;
}
};
@@ -73,46 +70,43 @@ protected void setValues(PreparedStatement ps, LobCreator lobCreator)
@Test
public void testAbstractLobStreamingResultSetExtractorNoRows() throws SQLException {
- ResultSet rset = mock(ResultSet.class);
+ ResultSet rs = mock(ResultSet.class);
AbstractLobStreamingResultSetExtractor lobRse = getResultSetExtractor(false);
- assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(() ->
- lobRse.extractData(rset));
- verify(rset).next();
+ assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class)
+ .isThrownBy(() -> lobRse.extractData(rs));
+ verify(rs).next();
}
@Test
public void testAbstractLobStreamingResultSetExtractorOneRow() throws SQLException {
- ResultSet rset = mock(ResultSet.class);
- given(rset.next()).willReturn(true, false);
+ ResultSet rs = mock(ResultSet.class);
+ given(rs.next()).willReturn(true, false);
AbstractLobStreamingResultSetExtractor lobRse = getResultSetExtractor(false);
- lobRse.extractData(rset);
- verify(rset).clearWarnings();
+ lobRse.extractData(rs);
+ verify(rs).clearWarnings();
}
@Test
- public void testAbstractLobStreamingResultSetExtractorMultipleRows()
- throws SQLException {
- ResultSet rset = mock(ResultSet.class);
- given(rset.next()).willReturn(true, true, false);
+ public void testAbstractLobStreamingResultSetExtractorMultipleRows() throws SQLException {
+ ResultSet rs = mock(ResultSet.class);
+ given(rs.next()).willReturn(true, true, false);
AbstractLobStreamingResultSetExtractor lobRse = getResultSetExtractor(false);
- assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(() ->
- lobRse.extractData(rset));
- verify(rset).clearWarnings();
+ assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class)
+ .isThrownBy(() -> lobRse.extractData(rs));
+ verify(rs).clearWarnings();
}
@Test
- public void testAbstractLobStreamingResultSetExtractorCorrectException()
- throws SQLException {
- ResultSet rset = mock(ResultSet.class);
- given(rset.next()).willReturn(true);
+ public void testAbstractLobStreamingResultSetExtractorCorrectException() throws SQLException {
+ ResultSet rs = mock(ResultSet.class);
+ given(rs.next()).willReturn(true);
AbstractLobStreamingResultSetExtractor lobRse = getResultSetExtractor(true);
- assertThatExceptionOfType(LobRetrievalFailureException.class).isThrownBy(() ->
- lobRse.extractData(rset));
+ assertThatExceptionOfType(LobRetrievalFailureException.class)
+ .isThrownBy(() -> lobRse.extractData(rs));
}
private AbstractLobStreamingResultSetExtractor getResultSetExtractor(final boolean ex) {
AbstractLobStreamingResultSetExtractor lobRse = new AbstractLobStreamingResultSetExtractor() {
-
@Override
protected void streamData(ResultSet rs) throws SQLException, IOException {
if (ex) {
@@ -125,4 +119,5 @@ protected void streamData(ResultSet rs) throws SQLException, IOException {
};
return lobRse;
}
+
}
diff --git a/spring-jms/src/main/java/org/springframework/jms/connection/CachingConnectionFactory.java b/spring-jms/src/main/java/org/springframework/jms/connection/CachingConnectionFactory.java
index 3bab8917c78f..627ccfa2e33f 100644
--- a/spring-jms/src/main/java/org/springframework/jms/connection/CachingConnectionFactory.java
+++ b/spring-jms/src/main/java/org/springframework/jms/connection/CachingConnectionFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -622,7 +622,7 @@ public boolean equals(@Nullable Object other) {
@Override
public int hashCode() {
- return (31 * super.hashCode() + ObjectUtils.nullSafeHashCode(this.selector));
+ return super.hashCode() * 31 + ObjectUtils.nullSafeHashCode(this.selector);
}
@Override
diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/AbstractJmsListeningContainer.java b/spring-jms/src/main/java/org/springframework/jms/listener/AbstractJmsListeningContainer.java
index 0be44d55b725..21193f3b6401 100644
--- a/spring-jms/src/main/java/org/springframework/jms/listener/AbstractJmsListeningContainer.java
+++ b/spring-jms/src/main/java/org/springframework/jms/listener/AbstractJmsListeningContainer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/reactive/PayloadMethodArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/reactive/PayloadMethodArgumentResolver.java
index 3e3c930f05e5..4912e672d873 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/reactive/PayloadMethodArgumentResolver.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/reactive/PayloadMethodArgumentResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,7 +34,6 @@
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType;
-import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.codec.Decoder;
import org.springframework.core.codec.DecodingException;
import org.springframework.core.io.buffer.DataBuffer;
@@ -55,17 +54,17 @@
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.SmartValidator;
import org.springframework.validation.Validator;
-import org.springframework.validation.annotation.Validated;
+import org.springframework.validation.annotation.ValidationAnnotationUtils;
/**
* A resolver to extract and decode the payload of a message using a
- * {@link Decoder}, where the payload is expected to be a {@link Publisher} of
- * {@link DataBuffer DataBuffer}.
+ * {@link Decoder}, where the payload is expected to be a {@link Publisher}
+ * of {@link DataBuffer DataBuffer}.
*
* Validation is applied if the method argument is annotated with
- * {@code @javax.validation.Valid} or
- * {@link org.springframework.validation.annotation.Validated}. Validation
- * failure results in an {@link MethodArgumentNotValidException}.
+ * {@link org.springframework.validation.annotation.Validated} or
+ * {@code @javax.validation.Valid}. Validation failure results in an
+ * {@link MethodArgumentNotValidException}.
*
*
This resolver should be ordered last if {@link #useDefaultResolution} is
* set to {@code true} since in that case it supports all types and does not
@@ -287,10 +286,8 @@ private Consumer getValidator(Message> message, MethodParameter parame
return null;
}
for (Annotation ann : parameter.getParameterAnnotations()) {
- Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
- if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
- Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
- Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
+ Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
+ if (validationHints != null) {
String name = Conventions.getVariableNameForParameter(parameter);
return target -> {
BeanPropertyBindingResult bindingResult = new BeanPropertyBindingResult(target, name);
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadMethodArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadMethodArgumentResolver.java
index 2ced8c86cd45..3ef7b86c189d 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadMethodArgumentResolver.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadMethodArgumentResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@
import java.lang.annotation.Annotation;
import org.springframework.core.MethodParameter;
-import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.converter.MessageConversionException;
@@ -36,12 +35,16 @@
import org.springframework.validation.ObjectError;
import org.springframework.validation.SmartValidator;
import org.springframework.validation.Validator;
-import org.springframework.validation.annotation.Validated;
+import org.springframework.validation.annotation.ValidationAnnotationUtils;
/**
* A resolver to extract and convert the payload of a message using a
- * {@link MessageConverter}. It also validates the payload using a
- * {@link Validator} if the argument is annotated with a Validation annotation.
+ * {@link MessageConverter}.
+ *
+ * Validation is applied if the method argument is annotated with
+ * {@link org.springframework.validation.annotation.Validated} or
+ * {@code @javax.validation.Valid}. Validation failure results in an
+ * {@link MethodArgumentNotValidException}.
*
*
This {@link HandlerMethodArgumentResolver} should be ordered last as it
* supports all types and does not require the {@link Payload} annotation.
@@ -190,9 +193,6 @@ protected Class> resolveTargetClass(MethodParameter parameter, Message> mess
/**
* Validate the payload if applicable.
- *
The default implementation checks for {@code @javax.validation.Valid},
- * Spring's {@link Validated},
- * and custom annotations whose name starts with "Valid".
* @param message the currently processed message
* @param parameter the method parameter
* @param target the target payload object
@@ -203,10 +203,8 @@ protected void validate(Message> message, MethodParameter parameter, Object ta
return;
}
for (Annotation ann : parameter.getParameterAnnotations()) {
- Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
- if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
- Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
- Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
+ Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
+ if (validationHints != null) {
BeanPropertyBindingResult bindingResult =
new BeanPropertyBindingResult(target, getParameterName(parameter));
if (!ObjectUtils.isEmpty(validationHints) && this.validator instanceof SmartValidator) {
diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/SpringFlushSynchronization.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/SpringFlushSynchronization.java
index 94579851887d..cd64a15e80ed 100644
--- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/SpringFlushSynchronization.java
+++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/SpringFlushSynchronization.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,7 +43,6 @@ public void flush() {
SessionFactoryUtils.flush(this.session, false);
}
-
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof SpringFlushSynchronization &&
diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionFactoryUtils.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionFactoryUtils.java
index c69dc2f49e57..33a9b6bf8238 100644
--- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionFactoryUtils.java
+++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionFactoryUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -85,7 +85,7 @@ public abstract class ConnectionFactoryUtils {
*/
public static Mono getConnection(ConnectionFactory connectionFactory) {
return doGetConnection(connectionFactory)
- .onErrorMap(e -> new DataAccessResourceFailureException("Failed to obtain R2DBC Connection", e));
+ .onErrorMap(ex -> new DataAccessResourceFailureException("Failed to obtain R2DBC Connection", ex));
}
/**
@@ -124,17 +124,17 @@ public static Mono doGetConnection(ConnectionFactory connectionFacto
holderToUse.setConnection(conn);
}
holderToUse.requested();
- synchronizationManager
- .registerSynchronization(new ConnectionSynchronization(holderToUse, connectionFactory));
+ synchronizationManager.registerSynchronization(
+ new ConnectionSynchronization(holderToUse, connectionFactory));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
synchronizationManager.bindResource(connectionFactory, holderToUse);
}
}) // Unexpected exception from external delegation call -> close Connection and rethrow.
- .onErrorResume(e -> releaseConnection(connection, connectionFactory).then(Mono.error(e))));
+ .onErrorResume(ex -> releaseConnection(connection, connectionFactory).then(Mono.error(ex))));
}
return con;
- }).onErrorResume(NoTransactionException.class, e -> Mono.from(connectionFactory.create()));
+ }).onErrorResume(NoTransactionException.class, ex -> Mono.from(connectionFactory.create()));
}
/**
@@ -159,7 +159,7 @@ private static Mono fetchConnection(ConnectionFactory connectionFact
*/
public static Mono releaseConnection(Connection con, ConnectionFactory connectionFactory) {
return doReleaseConnection(con, connectionFactory)
- .onErrorMap(e -> new DataAccessResourceFailureException("Failed to close R2DBC Connection", e));
+ .onErrorMap(ex -> new DataAccessResourceFailureException("Failed to close R2DBC Connection", ex));
}
/**
@@ -171,15 +171,14 @@ public static Mono releaseConnection(Connection con, ConnectionFactory con
* @see #doGetConnection
*/
public static Mono doReleaseConnection(Connection connection, ConnectionFactory connectionFactory) {
- return TransactionSynchronizationManager.forCurrentTransaction()
- .flatMap(synchronizationManager -> {
+ return TransactionSynchronizationManager.forCurrentTransaction().flatMap(synchronizationManager -> {
ConnectionHolder conHolder = (ConnectionHolder) synchronizationManager.getResource(connectionFactory);
if (conHolder != null && connectionEquals(conHolder, connection)) {
// It's the transactional Connection: Don't close it.
conHolder.released();
}
return Mono.from(connection.close());
- }).onErrorResume(NoTransactionException.class, e -> Mono.from(connection.close()));
+ }).onErrorResume(NoTransactionException.class, ex -> Mono.from(connection.close()));
}
/**
@@ -268,7 +267,8 @@ private static boolean connectionEquals(ConnectionHolder conHolder, Connection p
Connection heldCon = conHolder.getConnection();
// Explicitly check for identity too: for Connection handles that do not implement
// "equals" properly).
- return (heldCon == passedInCon || heldCon.equals(passedInCon) || getTargetConnection(heldCon).equals(passedInCon));
+ return (heldCon == passedInCon || heldCon.equals(passedInCon) ||
+ getTargetConnection(heldCon).equals(passedInCon));
}
/**
diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/init/DatabasePopulator.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/init/DatabasePopulator.java
index 467a25549cfd..8adf9831b30c 100644
--- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/init/DatabasePopulator.java
+++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/init/DatabasePopulator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@
import io.r2dbc.spi.ConnectionFactory;
import reactor.core.publisher.Mono;
-import org.springframework.dao.DataAccessException;
import org.springframework.r2dbc.connection.ConnectionFactoryUtils;
import org.springframework.util.Assert;
@@ -44,17 +43,18 @@ public interface DatabasePopulator {
* already configured and ready to use, must not be {@code null}
* @return {@link Mono} that initiates script execution and is
* notified upon completion
- * @throws ScriptException in all other error cases
+ * @throws ScriptException in case of any errors
*/
- Mono populate(Connection connection) throws ScriptException;
+ Mono populate(Connection connection);
/**
* Execute the given {@link DatabasePopulator} against the given {@link ConnectionFactory}.
* @param connectionFactory the {@link ConnectionFactory} to execute against
* @return {@link Mono} that initiates {@link DatabasePopulator#populate(Connection)}
* and is notified upon completion
+ * @throws ScriptException in case of any errors
*/
- default Mono populate(ConnectionFactory connectionFactory) throws DataAccessException {
+ default Mono populate(ConnectionFactory connectionFactory) {
Assert.notNull(connectionFactory, "ConnectionFactory must not be null");
return Mono.usingWhen(ConnectionFactoryUtils.getConnection(connectionFactory), //
this::populate, //
diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/init/ResourceDatabasePopulator.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/init/ResourceDatabasePopulator.java
index a6aeac31364c..86dc2f4fcf23 100644
--- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/init/ResourceDatabasePopulator.java
+++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/init/ResourceDatabasePopulator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -260,7 +260,7 @@ public void setDataBufferFactory(DataBufferFactory dataBufferFactory) {
@Override
- public Mono populate(Connection connection) throws ScriptException {
+ public Mono populate(Connection connection) {
Assert.notNull(connection, "Connection must not be null");
return Flux.fromIterable(this.scripts).concatMap(resource -> {
EncodedResource encodedScript = new EncodedResource(resource, this.sqlScriptEncoding);
diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/init/ScriptUtils.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/init/ScriptUtils.java
index c374d68c05a4..18437ddca93a 100644
--- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/init/ScriptUtils.java
+++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/init/ScriptUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -125,7 +125,7 @@ public abstract class ScriptUtils {
* @see org.springframework.r2dbc.connection.ConnectionFactoryUtils#getConnection
* @see org.springframework.r2dbc.connection.ConnectionFactoryUtils#releaseConnection
*/
- public static Mono executeSqlScript(Connection connection, Resource resource) throws ScriptException {
+ public static Mono executeSqlScript(Connection connection, Resource resource) {
return executeSqlScript(connection, new EncodedResource(resource));
}
@@ -149,7 +149,7 @@ public static Mono executeSqlScript(Connection connection, Resource resour
* @see org.springframework.r2dbc.connection.ConnectionFactoryUtils#getConnection
* @see org.springframework.r2dbc.connection.ConnectionFactoryUtils#releaseConnection
*/
- public static Mono executeSqlScript(Connection connection, EncodedResource resource) throws ScriptException {
+ public static Mono executeSqlScript(Connection connection, EncodedResource resource) {
return executeSqlScript(connection, resource, DefaultDataBufferFactory.sharedInstance, false, false,
DEFAULT_COMMENT_PREFIXES, DEFAULT_STATEMENT_SEPARATOR, DEFAULT_BLOCK_COMMENT_START_DELIMITER,
DEFAULT_BLOCK_COMMENT_END_DELIMITER);
@@ -189,7 +189,7 @@ public static Mono executeSqlScript(Connection connection, EncodedResource
public static Mono executeSqlScript(Connection connection, EncodedResource resource,
DataBufferFactory dataBufferFactory, boolean continueOnError, boolean ignoreFailedDrops,
String commentPrefix, @Nullable String separator, String blockCommentStartDelimiter,
- String blockCommentEndDelimiter) throws ScriptException {
+ String blockCommentEndDelimiter) {
return executeSqlScript(connection, resource, dataBufferFactory, continueOnError,
ignoreFailedDrops, new String[] { commentPrefix }, separator,
@@ -230,7 +230,7 @@ public static Mono executeSqlScript(Connection connection, EncodedResource
public static Mono executeSqlScript(Connection connection, EncodedResource resource,
DataBufferFactory dataBufferFactory, boolean continueOnError, boolean ignoreFailedDrops,
String[] commentPrefixes, @Nullable String separator, String blockCommentStartDelimiter,
- String blockCommentEndDelimiter) throws ScriptException {
+ String blockCommentEndDelimiter) {
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL script from " + resource);
@@ -365,7 +365,7 @@ private static void appendSeparatorToScriptIfNecessary(StringBuilder scriptBuild
*/
static boolean containsStatementSeparator(EncodedResource resource, String script,
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
- String blockCommentEndDelimiter) throws ScriptException {
+ String blockCommentEndDelimiter) {
boolean inSingleQuote = false;
boolean inDoubleQuote = false;
@@ -448,7 +448,7 @@ else if (script.startsWith(blockCommentStartDelimiter, i)) {
*/
static List splitSqlScript(EncodedResource resource, String script,
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
- String blockCommentEndDelimiter) throws ScriptException {
+ String blockCommentEndDelimiter) {
Assert.hasText(script, "'script' must not be null or empty");
Assert.notNull(separator, "'separator' must not be null");
diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/ConnectionAccessor.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/ConnectionAccessor.java
index 9f794ce67101..d3424708960d 100644
--- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/ConnectionAccessor.java
+++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/ConnectionAccessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,8 +22,6 @@
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
-import org.springframework.dao.DataAccessException;
-
/**
* Interface declaring methods that accept callback {@link Function}
* to operate within the scope of a {@link Connection}.
@@ -31,13 +29,16 @@
* close the connection as the connections may be pooled or be
* subject to other kinds of resource management.
*
- * Callback functions are responsible for creating a
+ *
Callback functions are responsible for creating a
* {@link org.reactivestreams.Publisher} that defines the scope of how
* long the allocated {@link Connection} is valid. Connections are
* released after the publisher terminates.
*
+ *
This serves as a base interface for {@link DatabaseClient}.
+ *
* @author Mark Paluch
* @since 5.3
+ * @see DatabaseClient
*/
public interface ConnectionAccessor {
@@ -49,8 +50,9 @@ public interface ConnectionAccessor {
* {@link Function} closure, otherwise resources may get defunct.
* @param action the callback object that specifies the connection action
* @return the resulting {@link Mono}
+ * @throws org.springframework.dao.DataAccessException in case of any errors
*/
- Mono inConnection(Function> action) throws DataAccessException;
+ Mono inConnection(Function> action);
/**
* Execute a callback {@link Function} within a {@link Connection} scope.
@@ -60,7 +62,8 @@ public interface ConnectionAccessor {
* {@link Function} closure, otherwise resources may get defunct.
* @param action the callback object that specifies the connection action
* @return the resulting {@link Flux}
+ * @throws org.springframework.dao.DataAccessException in case of any errors
*/
- Flux inConnectionMany(Function> action) throws DataAccessException;
+ Flux inConnectionMany(Function> action);
}
diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DatabaseClient.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DatabaseClient.java
index 71569e190180..f9a6daef4d18 100644
--- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DatabaseClient.java
+++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DatabaseClient.java
@@ -32,9 +32,9 @@
import org.springframework.util.Assert;
/**
- * A non-blocking, reactive client for performing database calls requests with
- * Reactive Streams back pressure. Provides a higher level, common API over
- * R2DBC client libraries.
+ * A non-blocking, reactive client for performing database calls with Reactive Streams
+ * back pressure. Provides a higher level, common API over R2DBC client libraries.
+ * Propagates {@link org.springframework.dao.DataAccessException} variants for errors.
*
* Use one of the static factory methods {@link #create(ConnectionFactory)}
* or obtain a {@link DatabaseClient#builder()} to create an instance.
diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DefaultDatabaseClient.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DefaultDatabaseClient.java
index 5a552ea7fa66..6d3c33673093 100644
--- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DefaultDatabaseClient.java
+++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DefaultDatabaseClient.java
@@ -44,7 +44,6 @@
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
-import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.lang.Nullable;
import org.springframework.r2dbc.connection.ConnectionFactoryUtils;
@@ -105,7 +104,7 @@ public GenericExecuteSpec sql(Supplier sqlSupplier) {
}
@Override
- public Mono inConnection(Function> action) throws DataAccessException {
+ public Mono inConnection(Function> action) {
Assert.notNull(action, "Callback object must not be null");
Mono connectionMono = getConnection().map(
connection -> new ConnectionCloseHolder(connection, this::closeConnection));
@@ -127,7 +126,7 @@ public Mono inConnection(Function> action) throws Dat
}
@Override
- public Flux inConnectionMany(Function> action) throws DataAccessException {
+ public Flux inConnectionMany(Function> action) {
Assert.notNull(action, "Callback object must not be null");
Mono connectionMono = getConnection().map(
connection -> new ConnectionCloseHolder(connection, this::closeConnection));
diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java
index a9c3231eaf18..ce9d1bc5a195 100644
--- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java
+++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/NamedParameterUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
@@ -373,6 +372,7 @@ private static class ParameterHolder {
private final int endIndex;
ParameterHolder(String parameterName, int startIndex, int endIndex) {
+ Assert.notNull(parameterName, "Parameter name must not be null");
this.parameterName = parameterName;
this.startIndex = startIndex;
this.endIndex = endIndex;
@@ -391,21 +391,21 @@ int getEndIndex() {
}
@Override
- public boolean equals(Object o) {
- if (this == o) {
+ public boolean equals(Object other) {
+ if (this == other) {
return true;
}
- if (!(o instanceof ParameterHolder)) {
+ if (!(other instanceof ParameterHolder)) {
return false;
}
- ParameterHolder that = (ParameterHolder) o;
- return this.startIndex == that.startIndex && this.endIndex == that.endIndex
- && Objects.equals(this.parameterName, that.parameterName);
+ ParameterHolder that = (ParameterHolder) other;
+ return (this.startIndex == that.startIndex && this.endIndex == that.endIndex &&
+ this.parameterName.equals(that.parameterName));
}
@Override
public int hashCode() {
- return Objects.hash(this.parameterName, this.startIndex, this.endIndex);
+ return this.parameterName.hashCode();
}
}
diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/Parameter.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/Parameter.java
index 7615477fcedf..bea746611a3b 100644
--- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/Parameter.java
+++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/Parameter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,8 +16,6 @@
package org.springframework.r2dbc.core;
-import java.util.Objects;
-
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -109,21 +107,21 @@ public boolean isEmpty() {
@Override
- public boolean equals(Object obj) {
- if (this == obj) {
+ public boolean equals(Object other) {
+ if (this == other) {
return true;
}
- if (!(obj instanceof Parameter)) {
+ if (!(other instanceof Parameter)) {
return false;
}
- Parameter other = (Parameter) obj;
- return (ObjectUtils.nullSafeEquals(this.value, other.value) &&
- ObjectUtils.nullSafeEquals(this.type, other.type));
+ Parameter that = (Parameter) other;
+ return (ObjectUtils.nullSafeEquals(this.value, that.value) &&
+ ObjectUtils.nullSafeEquals(this.type, that.type));
}
@Override
public int hashCode() {
- return Objects.hash(this.value, this.type);
+ return ObjectUtils.nullSafeHashCode(this.value) + ObjectUtils.nullSafeHashCode(this.type);
}
@Override
diff --git a/spring-r2dbc/src/test/java/org/springframework/r2dbc/connection/R2dbcTransactionManagerUnitTests.java b/spring-r2dbc/src/test/java/org/springframework/r2dbc/connection/R2dbcTransactionManagerUnitTests.java
index 9f6cfd890549..1c99636646c8 100644
--- a/spring-r2dbc/src/test/java/org/springframework/r2dbc/connection/R2dbcTransactionManagerUnitTests.java
+++ b/spring-r2dbc/src/test/java/org/springframework/r2dbc/connection/R2dbcTransactionManagerUnitTests.java
@@ -24,7 +24,6 @@
import io.r2dbc.spi.R2dbcBadGrammarException;
import io.r2dbc.spi.R2dbcTimeoutException;
import io.r2dbc.spi.Statement;
-import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
@@ -55,6 +54,7 @@
* Unit tests for {@link R2dbcTransactionManager}.
*
* @author Mark Paluch
+ * @author Juergen Hoeller
*/
class R2dbcTransactionManagerUnitTests {
@@ -85,8 +85,7 @@ void testSimpleTransaction() {
ConnectionFactoryUtils.getConnection(connectionFactoryMock)
.flatMap(connection -> TransactionSynchronizationManager.forCurrentTransaction()
- .doOnNext(synchronizationManager -> synchronizationManager.registerSynchronization(
- sync)))
+ .doOnNext(synchronizationManager -> synchronizationManager.registerSynchronization(sync)))
.as(operator::transactional)
.as(StepVerifier::create)
.expectNextCount(1)
@@ -118,12 +117,11 @@ void testBeginFails() {
TransactionalOperator operator = TransactionalOperator.create(tm, definition);
- ConnectionFactoryUtils.getConnection(connectionFactoryMock).as(
- operator::transactional)
+ ConnectionFactoryUtils.getConnection(connectionFactoryMock)
+ .as(operator::transactional)
.as(StepVerifier::create)
.expectErrorSatisfies(actual -> assertThat(actual).isInstanceOf(
- CannotCreateTransactionException.class).hasCauseInstanceOf(
- R2dbcBadGrammarException.class))
+ CannotCreateTransactionException.class).hasCauseInstanceOf(R2dbcBadGrammarException.class))
.verify();
}
@@ -139,8 +137,8 @@ void appliesIsolationLevel() {
TransactionalOperator operator = TransactionalOperator.create(tm, definition);
- ConnectionFactoryUtils.getConnection(connectionFactoryMock).as(
- operator::transactional)
+ ConnectionFactoryUtils.getConnection(connectionFactoryMock)
+ .as(operator::transactional)
.as(StepVerifier::create)
.expectNextCount(1)
.verifyComplete();
@@ -164,8 +162,8 @@ void doesNotSetIsolationLevelIfMatch() {
TransactionalOperator operator = TransactionalOperator.create(tm, definition);
- ConnectionFactoryUtils.getConnection(connectionFactoryMock).as(
- operator::transactional)
+ ConnectionFactoryUtils.getConnection(connectionFactoryMock)
+ .as(operator::transactional)
.as(StepVerifier::create)
.expectNextCount(1)
.verifyComplete();
@@ -184,8 +182,8 @@ void doesNotSetAutoCommitDisabled() {
TransactionalOperator operator = TransactionalOperator.create(tm, definition);
- ConnectionFactoryUtils.getConnection(connectionFactoryMock).as(
- operator::transactional)
+ ConnectionFactoryUtils.getConnection(connectionFactoryMock)
+ .as(operator::transactional)
.as(StepVerifier::create)
.expectNextCount(1)
.verifyComplete();
@@ -232,8 +230,8 @@ void appliesReadOnly() {
TransactionalOperator operator = TransactionalOperator.create(tm, definition);
- ConnectionFactoryUtils.getConnection(connectionFactoryMock).as(
- operator::transactional)
+ ConnectionFactoryUtils.getConnection(connectionFactoryMock)
+ .as(operator::transactional)
.as(StepVerifier::create)
.expectNextCount(1)
.verifyComplete();
@@ -249,7 +247,6 @@ void appliesReadOnly() {
@Test
void testCommitFails() {
when(connectionMock.commitTransaction()).thenReturn(Mono.defer(() -> Mono.error(new R2dbcBadGrammarException("Commit should fail"))));
-
when(connectionMock.rollbackTransaction()).thenReturn(Mono.empty());
TransactionalOperator operator = TransactionalOperator.create(tm);
@@ -270,7 +267,6 @@ void testCommitFails() {
@Test
void testRollback() {
-
AtomicInteger commits = new AtomicInteger();
when(connectionMock.commitTransaction()).thenReturn(
Mono.fromRunnable(commits::incrementAndGet));
@@ -282,11 +278,9 @@ void testRollback() {
TransactionalOperator operator = TransactionalOperator.create(tm);
ConnectionFactoryUtils.getConnection(connectionFactoryMock)
- .doOnNext(connection -> {
- throw new IllegalStateException();
- }).as(operator::transactional)
- .as(StepVerifier::create)
- .verifyError(IllegalStateException.class);
+ .doOnNext(connection -> { throw new IllegalStateException(); })
+ .as(operator::transactional)
+ .as(StepVerifier::create).verifyError(IllegalStateException.class);
assertThat(commits).hasValue(0);
assertThat(rollbacks).hasValue(1);
@@ -303,15 +297,11 @@ void testRollbackFails() {
when(connectionMock.rollbackTransaction()).thenReturn(Mono.defer(() -> Mono.error(new R2dbcBadGrammarException("Commit should fail"))), Mono.empty());
TransactionalOperator operator = TransactionalOperator.create(tm);
-
operator.execute(reactiveTransaction -> {
-
reactiveTransaction.setRollbackOnly();
-
return ConnectionFactoryUtils.getConnection(connectionFactoryMock)
.doOnNext(connection -> connection.createStatement("foo")).then();
- }).as(StepVerifier::create)
- .verifyError(IllegalTransactionStateException.class);
+ }).as(StepVerifier::create).verifyError(IllegalTransactionStateException.class);
verify(connectionMock).isAutoCommit();
verify(connectionMock).beginTransaction();
@@ -338,7 +328,7 @@ void testConnectionReleasedWhenRollbackFails() {
.doOnNext(connection -> {
throw new IllegalStateException("Intentional error to trigger rollback");
}).then()).as(StepVerifier::create)
- .verifyErrorSatisfies(e -> Assertions.assertThat(e)
+ .verifyErrorSatisfies(ex -> assertThat(ex)
.isInstanceOf(BadSqlGrammarException.class)
.hasCause(new R2dbcBadGrammarException("Rollback should fail"))
);
@@ -357,19 +347,15 @@ void testTransactionSetRollbackOnly() {
TransactionSynchronization.STATUS_ROLLED_BACK);
TransactionalOperator operator = TransactionalOperator.create(tm);
-
operator.execute(tx -> {
-
tx.setRollbackOnly();
assertThat(tx.isNewTransaction()).isTrue();
-
return TransactionSynchronizationManager.forCurrentTransaction().doOnNext(
synchronizationManager -> {
assertThat(synchronizationManager.hasResource(connectionFactoryMock)).isTrue();
synchronizationManager.registerSynchronization(sync);
}).then();
- }).as(StepVerifier::create)
- .verifyComplete();
+ }).as(StepVerifier::create).verifyComplete();
verify(connectionMock).isAutoCommit();
verify(connectionMock).beginTransaction();
@@ -389,20 +375,16 @@ void testPropagationNeverWithExistingTransaction() {
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
- TransactionalOperator operator = TransactionalOperator.create(tm, definition);
+ TransactionalOperator operator = TransactionalOperator.create(tm, definition);
operator.execute(tx1 -> {
-
assertThat(tx1.isNewTransaction()).isTrue();
-
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_NEVER);
return operator.execute(tx2 -> {
-
fail("Should have thrown IllegalTransactionStateException");
return Mono.empty();
});
- }).as(StepVerifier::create)
- .verifyError(IllegalTransactionStateException.class);
+ }).as(StepVerifier::create).verifyError(IllegalTransactionStateException.class);
verify(connectionMock).rollbackTransaction();
verify(connectionMock).close();
@@ -414,32 +396,49 @@ void testPropagationSupportsAndRequiresNew() {
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
- TransactionalOperator operator = TransactionalOperator.create(tm, definition);
+ TransactionalOperator operator = TransactionalOperator.create(tm, definition);
operator.execute(tx1 -> {
-
assertThat(tx1.isNewTransaction()).isFalse();
-
DefaultTransactionDefinition innerDef = new DefaultTransactionDefinition();
- innerDef.setPropagationBehavior(
- TransactionDefinition.PROPAGATION_REQUIRES_NEW);
+ innerDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
TransactionalOperator inner = TransactionalOperator.create(tm, innerDef);
-
return inner.execute(tx2 -> {
-
assertThat(tx2.isNewTransaction()).isTrue();
return Mono.empty();
});
- }).as(StepVerifier::create)
- .verifyComplete();
+ }).as(StepVerifier::create).verifyComplete();
verify(connectionMock).commitTransaction();
verify(connectionMock).close();
}
+ @Test
+ void testPropagationSupportsAndRequiresNewWithRollback() {
+ when(connectionMock.rollbackTransaction()).thenReturn(Mono.empty());
- private static class TestTransactionSynchronization
- implements TransactionSynchronization {
+ DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
+ definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
+
+ TransactionalOperator operator = TransactionalOperator.create(tm, definition);
+ operator.execute(tx1 -> {
+ assertThat(tx1.isNewTransaction()).isFalse();
+ DefaultTransactionDefinition innerDef = new DefaultTransactionDefinition();
+ innerDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
+ TransactionalOperator inner = TransactionalOperator.create(tm, innerDef);
+ return inner.execute(tx2 -> {
+ assertThat(tx2.isNewTransaction()).isTrue();
+ tx2.setRollbackOnly();
+ return Mono.empty();
+ });
+ }).as(StepVerifier::create).verifyComplete();
+
+ verify(connectionMock).rollbackTransaction();
+ verify(connectionMock).close();
+ }
+
+
+ private static class TestTransactionSynchronization implements TransactionSynchronization {
private int status;
@@ -512,7 +511,6 @@ protected void doAfterCompletion(int status) {
this.afterCompletionCalled = true;
assertThat(status).isEqualTo(this.status);
}
-
}
}
diff --git a/spring-test/src/main/java/org/springframework/mock/env/MockEnvironment.java b/spring-test/src/main/java/org/springframework/mock/env/MockEnvironment.java
index 8495e7c37b06..88072db943d3 100644
--- a/spring-test/src/main/java/org/springframework/mock/env/MockEnvironment.java
+++ b/spring-test/src/main/java/org/springframework/mock/env/MockEnvironment.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,8 +21,7 @@
/**
* Simple {@link ConfigurableEnvironment} implementation exposing
- * {@link #setProperty(String, String)} and {@link #withProperty(String, String)}
- * methods for testing purposes.
+ * {@link #setProperty} and {@link #withProperty} methods for testing purposes.
*
* @author Chris Beams
* @author Sam Brannen
@@ -31,7 +30,8 @@
*/
public class MockEnvironment extends AbstractEnvironment {
- private MockPropertySource propertySource = new MockPropertySource();
+ private final MockPropertySource propertySource = new MockPropertySource();
+
/**
* Create a new {@code MockEnvironment} with a single {@link MockPropertySource}.
@@ -40,6 +40,7 @@ public MockEnvironment() {
getPropertySources().addLast(this.propertySource);
}
+
/**
* Set a property on the underlying {@link MockPropertySource} for this environment.
*/
@@ -54,7 +55,7 @@ public void setProperty(String key, String value) {
* @see MockPropertySource#withProperty
*/
public MockEnvironment withProperty(String key, String value) {
- this.setProperty(key, value);
+ setProperty(key, value);
return this;
}
diff --git a/spring-test/src/main/java/org/springframework/mock/env/MockPropertySource.java b/spring-test/src/main/java/org/springframework/mock/env/MockPropertySource.java
index 2f3eb44151f8..3ef180fcf22b 100644
--- a/spring-test/src/main/java/org/springframework/mock/env/MockPropertySource.java
+++ b/spring-test/src/main/java/org/springframework/mock/env/MockPropertySource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,6 +48,7 @@ public class MockPropertySource extends PropertiesPropertySource {
*/
public static final String MOCK_PROPERTIES_PROPERTY_SOURCE_NAME = "mockProperties";
+
/**
* Create a new {@code MockPropertySource} named {@value #MOCK_PROPERTIES_PROPERTY_SOURCE_NAME}
* that will maintain its own internal {@link Properties} instance.
@@ -84,6 +85,7 @@ public MockPropertySource(String name, Properties properties) {
super(name, properties);
}
+
/**
* Set the given property on the underlying {@link Properties} object.
*/
@@ -97,7 +99,7 @@ public void setProperty(String name, Object value) {
* @return this {@link MockPropertySource} instance
*/
public MockPropertySource withProperty(String name, Object value) {
- this.setProperty(name, value);
+ setProperty(name, value);
return this;
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizer.java b/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizer.java
index d7093a066a7a..b419f0b9e412 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizer.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,7 +46,6 @@ class DynamicPropertiesContextCustomizer implements ContextCustomizer {
private static final String PROPERTY_SOURCE_NAME = "Dynamic Test Properties";
-
private final Set methods;
@@ -65,9 +64,7 @@ private void assertValid(Method method) {
}
@Override
- public void customizeContext(ConfigurableApplicationContext context,
- MergedContextConfiguration mergedConfig) {
-
+ public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
MutablePropertySources sources = context.getEnvironment().getPropertySources();
sources.addFirst(new DynamicValuesPropertySource(PROPERTY_SOURCE_NAME, buildDynamicPropertiesMap()));
}
@@ -90,10 +87,6 @@ Set getMethods() {
return this.methods;
}
- @Override
- public int hashCode() {
- return this.methods.hashCode();
- }
@Override
public boolean equals(Object obj) {
@@ -106,4 +99,9 @@ public boolean equals(Object obj) {
return this.methods.equals(((DynamicPropertiesContextCustomizer) obj).methods);
}
+ @Override
+ public int hashCode() {
+ return this.methods.hashCode();
+ }
+
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizerFactory.java b/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizerFactory.java
index 11779cc12339..7f9e3da7798a 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizerFactory.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizerFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@
*
* @author Phillip Webb
* @author Sam Brannen
+ * @author Yanming Zhou
* @since 5.2.5
* @see DynamicPropertiesContextCustomizer
*/
@@ -54,10 +55,15 @@ public DynamicPropertiesContextCustomizer createContextCustomizer(Class> testC
}
private void findMethods(Class> testClass, Set methods) {
- methods.addAll(MethodIntrospector.selectMethods(testClass, this::isAnnotated));
+ // Beginning with Java 16, inner classes may contain static members.
+ // We therefore need to search for @DynamicPropertySource methods in the
+ // current class after searching enclosing classes so that a local
+ // @DynamicPropertySource method can override properties registered in
+ // an enclosing class.
if (TestContextAnnotationUtils.searchEnclosingClass(testClass)) {
findMethods(testClass.getEnclosingClass(), methods);
}
+ methods.addAll(MethodIntrospector.selectMethods(testClass, this::isAnnotated));
}
private boolean isAnnotated(Method method) {
diff --git a/spring-tx/src/main/java/org/springframework/dao/support/DataAccessUtils.java b/spring-tx/src/main/java/org/springframework/dao/support/DataAccessUtils.java
index adb80a3afb7a..a72df8db9dc1 100644
--- a/spring-tx/src/main/java/org/springframework/dao/support/DataAccessUtils.java
+++ b/spring-tx/src/main/java/org/springframework/dao/support/DataAccessUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author 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,8 @@
/**
* Miscellaneous utility methods for DAO implementations.
- * Useful with any data access technology.
+ *
+ *