Compare commits

..

47 Commits

Author SHA1 Message Date
Spring Builds e5d99ecf98 Release v5.3.30 2023-09-14 07:43:24 +00:00
Juergen Hoeller f7bf2431fb Clarify IN clause resolution with List/Iterable parameter
Closes gh-31228
2023-09-14 09:28:54 +02:00
Sébastien Deleuze 40678bb981 Refine CORS documentation for wildcard processing
This commit adds a reference documentation section dedicated
to CORS credentialed requests and related wildcard processing.

Closes gh-31168
2023-09-11 18:22:03 +02:00
Sébastien Deleuze 75faf698af Refine CORS documentation for wildcard processing
This commit refines CORS wildcard processing Javadoc to
provides more details on how wildcards are handled for
Access-Control-Allow-Methods, Access-Control-Allow-Headers
and Access-Control-Expose-Headers CORS headers.

For Access-Control-Expose-Headers, it is not possible to copy
the response headers which are not available at the point
when the CorsProcessor is invoked. Since all the major browsers
seem to support wildcard including on requests with credentials,
and since this is ultimately the user-agent responsibility to
check on client-side what is authorized or not, Spring Framework
continues to support this use case.

See gh-31168
2023-09-11 18:15:07 +02:00
Juergen Hoeller 39c225c813 AnnotationUtils.clearCache() includes all annotation caches
Closes gh-31170

(cherry picked from commit 78fce80c43)
2023-09-11 17:49:56 +02:00
Juergen Hoeller 0c3d8d7a44 Align abstract method signatures with original Commons Logging API
Closes gh-31166

(cherry picked from commit 268043e9c9)
2023-09-11 17:49:35 +02:00
Sam Brannen ddcae04ad5 Do not invoke [Map|Collection].isEmpty() in nullSafeConciseToString()
gh-30811 introduced explicit support for collections and maps in
ObjectUtils.nullSafeConciseToString() by invoking isEmpty() on a Map or
Collection to determine which concise string representation should be
used. However, this caused a regression in which an exception was
thrown if the Map or Collection was a proxy generated by
AbstractFactoryBean to support <util:set />, <util:list />, and
<util:map /> in XML configuration.

This commit addresses this set of regressions by always returning
"[...]" or "{...}" for a Collection or Map, respectively, disregarding
whether the map is empty or not.

Closes gh-31156
2023-09-08 16:21:39 +02:00
Juergen Hoeller 994bbec0c3 Polishing 2023-08-23 18:57:17 +02:00
Juergen Hoeller afb378a59f Consistently throw ParseException instead of IllegalStateException
Closes gh-31097
2023-08-23 18:57:12 +02:00
Sébastien Deleuze a4fc7d3c11 Optimize ClassUtils#getMostSpecificMethod
This commit optimizes ClassUtils#getMostSpecificMethod which is
a method frequently invoked in typical Spring applications.

It refines ClassUtils#isOverridable by considering static and
final modifiers as non overridable and optimizes its implementation.

Closes gh-31100
2023-08-23 18:16:49 +02:00
Sam Brannen 88c3a788f3 Update copyright headers 2023-08-21 15:12:54 +02:00
Sam Brannen d7ac89ecc9 Revise contribution
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.

However, since Spring Framework 5.3.x is built using Java 8, this
commit removes DynamicPropertySourceOverridesEnclosingClassTests since
it declares a static method in a @Nested (inner) test class, which
results in a compiler error on Java 8.

See https://bugs.openjdk.org/browse/JDK-8254321
See gh-31085
2023-08-21 15:06:00 +02:00
Yanming Zhou 9894174960 Allow overriding dynamic property from enclosing class in nested test class
Prior to this commit, a dynamic property registered via a
@DynamicPropertySource method in a @Nested test class was not able to
override a property registered via a @DynamicPropertySource method in
the enclosing class.

See gh-26091
Closes gh-31083
2023-08-21 14:52:58 +02:00
Juergen Hoeller 493f75e892 Optimize whitespace checks in StringUtils (as far as possible on JDK 8)
Closes gh-31067
2023-08-18 11:40:19 +02:00
Juergen Hoeller df066d8190 Fix accidental javadoc references to jakarta packages 2023-08-16 13:03:23 +02:00
Juergen Hoeller 5f7a6a0f38 Align validation metadata handling in PayloadMethodArgumentResolver
Reuses ValidationAnnotationUtils which is slightly optimized for the detection of Spring's Validated annotation now, also to the benefit of common web scenarios.

Closes gh-21852

(cherry picked from commit c7269feeaa)
2023-08-16 13:02:31 +02:00
Juergen Hoeller 4326c53222 Polishing
(cherry picked from commit 2ce75dc415)
2023-08-14 20:02:50 +02:00
Juergen Hoeller 3da7a35a91 Test factory-bean/method placeholders as well
See gh-20189

(cherry picked from commit 8b3ddeed05)
2023-08-14 19:41:48 +02:00
Juergen Hoeller b9be40ccd2 Add registerReactiveTypeOverride method to ReactiveAdapterRegistry
Closes gh-31047

(cherry picked from commit 389238f622)
2023-08-14 15:18:56 +02:00
Juergen Hoeller f7d4bd176e Explicit note on connection pool deadlock with REQUIRES_NEW
Closes gh-26250
2023-08-12 14:59:32 +02:00
Juergen Hoeller 2b48254268 Use extracted attributes instead of annotation access
See gh-31034

(cherry picked from commit d781f299c0)
2023-08-12 11:38:50 +02:00
Juergen Hoeller bb46b31925 Find TransactionalEventListener annotation on target method
Closes gh-31034

(cherry picked from commit 6fc4898a1b)
2023-08-12 11:38:41 +02:00
Juergen Hoeller 0c275107ea Cancel without interruption of currently running tasks
Leave potential interruption up to scheduler shutdown.

Closes gh-31019

(cherry picked from commit 6fc5a78252)
2023-08-10 00:08:39 +02:00
Juergen Hoeller 9931f442e4 Polishing
(cherry picked from commit 2aae0a4e0c)
2023-08-07 15:14:06 +02:00
Juergen Hoeller fc085e8663 Reinstate Introspector.flushFromCaches() call for JDK ClassInfo cache
Closes gh-27781
2023-08-07 15:07:33 +02:00
Juergen Hoeller 9d7154901f Polishing
(cherry picked from commit 6e5af9dccb)
2023-08-06 14:50:24 +02:00
Juergen Hoeller 24893d038f Polishing 2023-08-04 10:35:51 +02:00
Juergen Hoeller b9482375b7 Sort multiple @Autowired methods on same bean class via ASM
Closes gh-30359

(cherry picked from commit 7e6612a920)
2023-08-04 01:31:49 +02:00
Juergen Hoeller ad61fb75da Polishing 2023-08-02 01:24:11 +02:00
Juergen Hoeller c5aa7830bc Polishing
(cherry picked from commit abbea39855)
2023-07-27 21:58:22 +02:00
Juergen Hoeller 14c8c9168c Polishing 2023-07-26 14:02:05 +02:00
Juergen Hoeller 23eb0e3128 Polishing
(cherry picked from commit bbde68c49e)
2023-07-25 19:58:12 +02:00
Juergen Hoeller 840bd574db Polishing
(cherry picked from commit fdf1418dfb)
2023-07-24 11:40:59 +02:00
Juergen Hoeller 6dea580145 Clarify DataAccessException/ScriptException declarations for R2DBC
Closes gh-30932

(cherry picked from commit 5bcf5c6f7c)
2023-07-24 11:35:23 +02:00
Juergen Hoeller d1efc891db Upgrade to Netty 4.1.95 2023-07-22 00:43:26 +02:00
Juergen Hoeller c9849d6972 Polishing
(cherry picked from commit 3a9e0ea8a7)
2023-07-22 00:43:09 +02:00
Juergen Hoeller 790abeda1c Polishing 2023-07-21 20:40:06 +02:00
Juergen Hoeller 340b32a3cb Polishing 2023-07-19 23:31:38 +02:00
Juergen Hoeller a7b7466274 Polishing 2023-07-19 01:17:25 +02:00
Juergen Hoeller 3a8c0dbd8a Decouple exception messages for sync=true from @Cacheable
(cherry picked from commit bbcc788f60)
2023-07-18 22:12:58 +02:00
Juergen Hoeller b387d9bf10 MethodIntrospector handles overriding bridge method correctly
Closes gh-30906

(cherry picked from commit 616f728afa)
2023-07-18 13:27:15 +02:00
Juergen Hoeller 0f33f79c05 Avoid synchronization for shortcut re-resolution
See gh-30883

(cherry picked from commit 161a717639)
2023-07-16 16:26:37 +02:00
Juergen Hoeller ef65429823 Polishing
(cherry picked from commit 3a278cc66d)
2023-07-15 14:38:30 +02:00
Juergen Hoeller 0b4b313bae Cache DependencyDescriptor per autowired constructor argument
Aligned with shortcut handling in AutowiredAnnotationBeanPostProcessor.
Includes minor MethodInvoker optimization for pre-resolved targetClass.

Closes gh-30883

(cherry picked from commit 6183f06846)
2023-07-15 14:38:24 +02:00
Juergen Hoeller 6879be7508 Explicit hints for @PostConstruct methods (preventing deadlocks)
Closes gh-25074
2023-07-14 16:33:22 +02:00
Juergen Hoeller 355fa258bd Polishing 2023-07-14 14:52:15 +02:00
Spring Builds 60337738d8 Next development version (v5.3.30-SNAPSHOT) 2023-07-13 07:54:12 +00:00
156 changed files with 1910 additions and 1281 deletions
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.3.29
version=5.3.30
org.gradle.jvmargs=-Xmx2048m
org.gradle.caching=true
org.gradle.parallel=true
@@ -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 @@ class ScheduledAndTransactionalAnnotationIntegrationTests {
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 @@ class ScheduledAndTransactionalAnnotationIntegrationTests {
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 @@ class ScheduledAndTransactionalAnnotationIntegrationTests {
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 @@ class ScheduledAndTransactionalAnnotationIntegrationTests {
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();
@@ -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 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
/**
* If a pointcut expression has been specified in XML, the user cannot
* write {@code and} as "&&" (though &amp;&amp; will work).
* We also allow {@code and} between two pointcut sub-expressions.
* write "and" as "&&" (though {@code &amp;&amp;} will work).
* <p>We also allow "and" between two pointcut sub-expressions.
* <p>This method converts back to {@code &&} for the AspectJ pointcut parser.
*/
private String replaceBooleanOperators(String pcExpr) {
@@ -527,7 +527,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
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 class AspectJExpressionPointcut extends AbstractExpressionPointcut
@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);
@@ -424,10 +424,9 @@ class CglibAopProxy implements AopProxy, 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 {
@@ -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 class ThrowsAdviceInterceptorTests {
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 class ThrowsAdviceInterceptorTests {
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 class ThrowsAdviceInterceptorTests {
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);
}
@@ -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 @@ public final class CachedIntrospectionResults {
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;
}
@@ -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.AnnotationAttributes;
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<String> lookupMethodsChecked = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
private final Map<Class<?>, Constructor<?>[]> candidateConstructorsCache = new ConcurrentHashMap<>(256);
@@ -238,6 +246,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
"AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
}
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
this.metadataReaderFactory = new SimpleMetadataReaderFactory(this.beanFactory.getBeanClassLoader());
}
@@ -463,12 +472,11 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
return InjectionMetadata.EMPTY;
}
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
final List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;
do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
final List<InjectionMetadata.InjectedElement> fieldElements = new ArrayList<>();
ReflectionUtils.doWithLocalFields(targetClass, field -> {
MergedAnnotation<?> ann = findAutowiredAnnotation(field);
if (ann != null) {
@@ -479,10 +487,11 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
return;
}
boolean required = determineRequiredStatus(ann);
currElements.add(new AutowiredFieldElement(field, required));
fieldElements.add(new AutowiredFieldElement(field, required));
}
});
final List<InjectionMetadata.InjectedElement> methodElements = new ArrayList<>();
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
@@ -504,11 +513,12 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
}
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 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
return BeanFactoryUtils.beansOfTypeIncludingAncestors(this.beanFactory, type);
}
/**
* Sort the method elements via ASM for deterministic declaration order if possible.
*/
private List<InjectionMetadata.InjectedElement> sortMethodElements(
List<InjectionMetadata.InjectedElement> 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<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Autowired.class.getName());
if (asmMethods.size() >= methodElements.size()) {
List<InjectionMetadata.InjectedElement> candidateMethods = new ArrayList<>(methodElements);
List<InjectionMetadata.InjectedElement> selectedMethods = new ArrayList<>(asmMethods.size());
for (MethodMetadata asmMethod : asmMethods) {
for (Iterator<InjectionMetadata.InjectedElement> 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 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
Set<String> autowiredBeanNames = new LinkedHashSet<>(2);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
Object value;
@@ -670,8 +721,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
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 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
int argumentCount = method.getParameterCount();
Object[] arguments = new Object[argumentCount];
DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount];
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
Set<String> 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 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
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 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
if (!this.cached) {
if (arguments != null) {
DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, argumentCount);
registerDependentBeans(beanName, autowiredBeans);
if (autowiredBeans.size() == argumentCount) {
Iterator<String> it = autowiredBeans.iterator();
registerDependentBeans(beanName, autowiredBeanNames);
if (autowiredBeanNames.size() == argumentCount) {
Iterator<String> 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 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
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());
}
}
@@ -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 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
converter = bw;
}
Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
Set<String> autowiredBeanNames = new LinkedHashSet<>(propertyNames.length * 2);
for (String propertyName : propertyNames) {
try {
PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
@@ -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 @@ class BeanDefinitionValueResolver {
return resolveInnerBean(argName, innerBeanName, bd);
}
else if (value instanceof DependencyDescriptor) {
Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
Set<String> autowiredBeanNames = new LinkedHashSet<>(2);
Object result = this.beanFactory.resolveDependency(
(DependencyDescriptor) value, this.beanName, autowiredBeanNames, this.typeConverter);
for (String autowiredBeanName : autowiredBeanNames) {
@@ -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.TypeConverter;
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<InjectionPoint> currentInjectionPoint =
new NamedThreadLocal<>("Current injection point");
@@ -729,7 +724,7 @@ class ConstructorResolver {
ArgumentsHolder args = new ArgumentsHolder(paramTypes.length);
Set<ConstructorArgumentValues.ValueHolder> usedValueHolders = new HashSet<>(paramTypes.length);
Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
Set<String> allAutowiredBeanNames = new LinkedHashSet<>(paramTypes.length * 2);
for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {
Class<?> paramType = paramTypes[paramIndex];
@@ -764,8 +759,8 @@ class ConstructorResolver {
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 @@ class ConstructorResolver {
"] - 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<String> 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 @@ class ConstructorResolver {
}
}
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 @@ class ConstructorResolver {
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<String> 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 @@ class ConstructorResolver {
}
/**
* 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<String> 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 @@ class ConstructorResolver {
}
}
private void setShortcutIfPossible(
ConstructorDependencyDescriptor descriptor, Class<?> paramType, Set<String> 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<String> 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 @@ class ConstructorResolver {
}
}
/**
* 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);
}
}
}
@@ -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 class LookupOverride extends MethodOverride {
@Override
public int hashCode() {
return (29 * super.hashCode() + ObjectUtils.nullSafeHashCode(this.beanName));
return super.hashCode() * 29 + ObjectUtils.nullSafeHashCode(this.beanName);
}
@Override
@@ -1271,10 +1271,11 @@ class DefaultListableBeanFactoryTests {
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 @@ class DefaultListableBeanFactoryTests {
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 @@ class DefaultListableBeanFactoryTests {
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 @@ class DefaultListableBeanFactoryTests {
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 @@ class DefaultListableBeanFactoryTests {
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 @@ class DefaultListableBeanFactoryTests {
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 @@ class DefaultListableBeanFactoryTests {
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 @@ class DefaultListableBeanFactoryTests {
@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
@@ -72,6 +72,9 @@ import org.springframework.core.ResolvableType;
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 class AutowiredAnnotationBeanPostProcessorTests {
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 class AutowiredAnnotationBeanPostProcessorTests {
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,55 @@ public class AutowiredAnnotationBeanPostProcessorTests {
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();
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();
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();
@@ -194,17 +250,13 @@ public class AutowiredAnnotationBeanPostProcessorTests {
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();
assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"});
}
@Test
@@ -233,10 +285,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
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 class AutowiredAnnotationBeanPostProcessorTests {
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 class AutowiredAnnotationBeanPostProcessorTests {
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 class AutowiredAnnotationBeanPostProcessorTests {
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 class AutowiredAnnotationBeanPostProcessorTests {
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 class AutowiredAnnotationBeanPostProcessorTests {
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 class AutowiredAnnotationBeanPostProcessorTests {
.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 class AutowiredAnnotationBeanPostProcessorTests {
@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 class AutowiredAnnotationBeanPostProcessorTests {
@Required
@SuppressWarnings("deprecation")
public void setTestBean2(TestBean testBean2) {
super.setTestBean2(testBean2);
this.testBean2 = testBean2;
}
@Autowired
@@ -2498,6 +2677,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
@Autowired
protected void initBeanFactory(BeanFactory beanFactory) {
Assert.state(this.baseInjected, "Wrong initialization order");
this.beanFactory = beanFactory;
}
@@ -2804,6 +2984,21 @@ public class AutowiredAnnotationBeanPostProcessorTests {
}
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 class AutowiredAnnotationBeanPostProcessorTests {
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;
}
}
@@ -4,25 +4,27 @@
<beans default-lazy-init="true">
<bean name="beta" class="org.springframework.beans.factory.FactoryBeanTests$Beta" autowire="byType">
<property name="name" value="${myName}"/>
<property name="name" value="${myName}"/>
</bean>
<bean id="alpha" class="org.springframework.beans.factory.FactoryBeanTests$Alpha" autowire="byType"/>
<bean id="gamma" class="org.springframework.beans.factory.FactoryBeanTests$Gamma"/>
<bean id="betaFactory" class="org.springframework.beans.factory.FactoryBeanTests$BetaFactoryBean" autowire="constructor">
<property name="beta" ref="beta"/>
</bean>
<bean id="betaFactory" class="org.springframework.beans.factory.FactoryBeanTests$BetaFactoryBean" autowire="constructor">
<property name="beta" ref="beta"/>
</bean>
<bean id="gammaFactory" factory-bean="betaFactory" factory-method="getGamma"/>
<bean id="gammaFactory" factory-bean="${gammaFactory}" factory-method="${gamma}"/>
<bean id="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="properties">
<props>
<prop key="myName">yourName</prop>
</props>
</property>
</bean>
<bean id="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="properties">
<props>
<prop key="myName">yourName</prop>
<prop key="gammaFactory">betaFactory</prop>
<prop key="gamma">getGamma</prop>
</props>
</property>
</bean>
</beans>
@@ -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 class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOt
@Override
public int hashCode() {
return this.age;
return TestBean.class.hashCode();
}
@Override
@@ -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 class CaffeineCache extends AbstractValueAdaptingCache {
* given internal {@link com.github.benmanes.caffeine.cache.Cache} to use.
* @param name the name of the cache
* @param cache the backing Caffeine Cache instance
* @param allowNullValues whether to accept and convert {@code null}
* values for this cache
* @param allowNullValues whether to accept and convert {@code null} values
* for this cache
*/
public CaffeineCache(String name, com.github.benmanes.caffeine.cache.Cache<Object, Object> cache,
boolean allowNullValues) {
@@ -86,7 +86,7 @@ public class CaffeineCache extends AbstractValueAdaptingCache {
@SuppressWarnings("unchecked")
@Override
@Nullable
public <T> T get(Object key, final Callable<T> valueLoader) {
public <T> T get(Object key, Callable<T> valueLoader) {
return (T) fromStoreValue(this.cache.get(key, new LoadFunction(valueLoader)));
}
@@ -106,7 +106,7 @@ public class CaffeineCache extends AbstractValueAdaptingCache {
@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 @@ public class CaffeineCache extends AbstractValueAdaptingCache {
@Nullable
private final Object value;
private boolean called;
boolean called;
public PutIfAbsentFunction(@Nullable Object value) {
this.value = value;
@@ -159,16 +159,17 @@ public class CaffeineCache extends AbstractValueAdaptingCache {
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);
}
}
}
@@ -110,7 +110,7 @@ public class CaffeineCacheManager implements CacheManager {
* 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<Object, Object> caffeine) {
Assert.notNull(caffeine, "Caffeine must not be null");
@@ -121,7 +121,7 @@ public class CaffeineCacheManager implements CacheManager {
* 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 class CaffeineCacheManager implements CacheManager {
* 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 @@ public class CaffeineCacheManager implements CacheManager {
* 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<Object, Object> cacheLoader) {
@@ -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 class CaffeineCacheManagerTests {
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 class CaffeineCacheManagerTests {
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 class CaffeineCacheManagerTests {
assertThat(value.get()).isEqualTo("pong");
assertThatIllegalArgumentException().isThrownBy(() -> assertThat(cache1.get("foo")).isNull())
.withMessageContaining("I only know ping");
.withMessageContaining("I only know ping");
}
@Test
@@ -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 @@ class QuartzSupportTests {
trigger.setName("myTrigger");
trigger.setJobDetail(jobDetail);
trigger.setStartDelay(1);
trigger.setRepeatInterval(500);
trigger.setRepeatInterval(100);
trigger.setRepeatCount(1);
trigger.afterPropertiesSet();
@@ -126,14 +126,14 @@ class QuartzSupportTests {
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 @@ class QuartzSupportTests {
trigger.setName("myTrigger");
trigger.setJobDetail(jobDetail);
trigger.setStartDelay(1);
trigger.setRepeatInterval(500);
trigger.setRepeatInterval(100);
trigger.setRepeatCount(1);
trigger.afterPropertiesSet();
@@ -168,7 +168,7 @@ class QuartzSupportTests {
Thread.sleep(500);
assertThat(DummyJobBean.param).isEqualTo(10);
assertThat(DummyJobBean.count > 0).isTrue();
assertThat(DummyJobBean.count).isGreaterThan(0);
bean.destroy();
}
@@ -190,7 +190,7 @@ class QuartzSupportTests {
trigger.setName("myTrigger");
trigger.setJobDetail(jobDetail);
trigger.setStartDelay(1);
trigger.setRepeatInterval(500);
trigger.setRepeatInterval(100);
trigger.setRepeatCount(1);
trigger.afterPropertiesSet();
@@ -203,7 +203,7 @@ class QuartzSupportTests {
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 @@ class QuartzSupportTests {
trigger.setName("myTrigger");
trigger.setJobDetail(jobDetail);
trigger.setStartDelay(1);
trigger.setRepeatInterval(500);
trigger.setRepeatInterval(100);
trigger.setRepeatCount(1);
trigger.afterPropertiesSet();
@@ -239,7 +239,7 @@ class QuartzSupportTests {
Thread.sleep(500);
assertThat(DummyJob.param).isEqualTo(0);
assertThat(DummyJob.count == 0).isTrue();
assertThat(DummyJob.count).isEqualTo(0);
bean.destroy();
}
@@ -260,7 +260,7 @@ class QuartzSupportTests {
trigger.setName("myTrigger");
trigger.setJobDetail(jobDetail);
trigger.setStartDelay(1);
trigger.setRepeatInterval(500);
trigger.setRepeatInterval(100);
trigger.setRepeatCount(1);
trigger.afterPropertiesSet();
@@ -273,7 +273,7 @@ class QuartzSupportTests {
Thread.sleep(500);
assertThat(DummyJobBean.param).isEqualTo(10);
assertThat(DummyJobBean.count > 0).isTrue();
assertThat(DummyJobBean.count).isGreaterThan(0);
bean.destroy();
}
@@ -292,7 +292,7 @@ class QuartzSupportTests {
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();
}
@@ -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 @@ import org.springframework.lang.Nullable;
/**
* Interface that defines common cache operations.
*
* <b>Note:</b> 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}).
* <p>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.
*
* <p><b>Note:</b> 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 {
@@ -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 @@ public @interface Cacheable {
* <li>Only one cache may be specified</li>
* <li>No other cache-related operation can be combined</li>
* </ol>
* 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)
*/
@@ -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 @@ public abstract class AbstractCacheInvoker {
* 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);
}
}
@@ -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 abstract class CacheAspectSupport extends AbstractCacheInvoker
@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 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
}
/**
* 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> T getBean(String beanName, Class<T> expectedType) {
protected <T> T getBean(String name, Class<T> 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 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
}
}
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<CachePutRequest> cachePutRequests = new ArrayList<>();
// Collect puts from any @Cacheable miss, if no cached value is found
List<CachePutRequest> cachePutRequests = new ArrayList<>(1);
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
@@ -469,7 +468,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
private boolean hasCachePut(CacheOperationContexts contexts) {
// Evaluate the conditions *without* the result object because we don't have it yet...
Collection<CacheOperationContext> cachePutContexts = contexts.get(CachePutOperation.class);
Collection<CacheOperationContext> excluded = new ArrayList<>();
Collection<CacheOperationContext> excluded = new ArrayList<>(1);
for (CacheOperationContext context : cachePutContexts) {
try {
if (!context.isConditionPassing(CacheOperationExpressionEvaluator.RESULT_UNAVAILABLE)) {
@@ -522,9 +521,9 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
}
/**
* 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 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
/**
* 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<CacheOperationContext> contexts,
@@ -641,21 +640,22 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
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 abstract class CacheAspectSupport extends AbstractCacheInvoker
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 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
return this.cacheNames;
}
private Collection<String> createCacheNames(Collection<? extends Cache> caches) {
Collection<String> names = new ArrayList<>();
private Collection<String> prepareCacheNames(Collection<? extends Cache> caches) {
Collection<String> names = new ArrayList<>(caches.size());
for (Cache cache : caches) {
names.add(cache.getName());
}
@@ -885,13 +885,13 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
}
}
/**
* Internal holder class for recording that a cache method was invoked.
*/
private static class InvocationAwareResult {
boolean invoked;
}
}
@@ -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 @@ import org.springframework.cache.Cache;
/**
* Simple cache manager working against a given collection of caches.
* Useful for testing or simple caching declarations.
* <p>
* When using this implementation directly, i.e. not via a regular
*
* <p>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 {
@@ -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 class AnnotationConfigApplicationContext extends GenericApplicationContex
@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 class AnnotationConfigApplicationContext extends GenericApplicationContex
@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();
@@ -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 @@ final class BeanMethod extends ConfigurationMethod {
}
@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 @@ final class BeanMethod extends ConfigurationMethod {
return "BeanMethod: " + this.metadata;
}
private class NonOverridableMethodError extends Problem {
NonOverridableMethodError() {
@@ -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 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
* @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 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
* the given existing bean definition.
* <p>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
}
@@ -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 @@ class ConfigurationClassBeanDefinitionReader {
public ConfigurationClassBeanDefinition(RootBeanDefinition original,
ConfigurationClass configClass, MethodMetadata beanMethodMetadata, String derivedBeanName) {
super(original);
this.annotationMetadata = configClass.getMetadata();
this.factoryMethodMetadata = beanMethodMetadata;
@@ -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 @@ class ConfigurationClassParser {
this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();
Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());
if (asmMethods.size() >= beanMethods.size()) {
Set<MethodMetadata> candidateMethods = new LinkedHashSet<>(beanMethods);
Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());
for (MethodMetadata asmMethod : asmMethods) {
for (MethodMetadata beanMethod : beanMethods) {
for (Iterator<MethodMetadata> it = candidateMethods.iterator(); it.hasNext();) {
MethodMetadata beanMethod = it.next();
if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
selectedMethods.add(beanMethod);
it.remove();
break;
}
}
@@ -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 class SimpleApplicationEventMulticaster extends AbstractApplicationEventM
* to invoke each listener with.
* <p>Default is equivalent to {@link org.springframework.core.task.SyncTaskExecutor},
* executing all listeners synchronously in the calling thread.
* <p>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.
* <p>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 class SimpleApplicationEventMulticaster extends AbstractApplicationEventM
/**
* Return the current task executor for this multicaster.
* @since 2.0
*/
@Nullable
protected Executor getTaskExecutor() {
@@ -1083,6 +1083,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
// 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();
@@ -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 java.io.IOException;
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 @@ public abstract class AbstractXmlApplicationContext extends AbstractRefreshableC
// 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 @@ public abstract class AbstractXmlApplicationContext extends AbstractRefreshableC
}
/**
* 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.
* <p>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);
@@ -28,8 +28,8 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
/**
* 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.
*
* <p>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 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
* done manually or, more conveniently, through the {@code <task:annotation-driven/>}
* XML element or {@link EnableScheduling @EnableScheduling} annotation.
*
* <p>This annotation can be used as a <em>{@linkplain Repeatable repeatable}</em>
* 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.
*
* <p>This annotation may be used as a <em>meta-annotation</em> to create custom
* <em>composed annotations</em> with attribute overrides.
*
@@ -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 class 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 class ScheduledAnnotationBeanPostProcessor
}
if (tasks != null) {
for (ScheduledTask task : tasks) {
task.cancel();
task.cancel(false);
}
}
}
@@ -598,7 +598,7 @@ public class ScheduledAnnotationBeanPostProcessor
Collection<Set<ScheduledTask>> allTasks = this.scheduledTasks.values();
for (Set<ScheduledTask> tasks : allTasks) {
for (ScheduledTask task : tasks) {
task.cancel();
task.cancel(false);
}
}
this.scheduledTasks.clear();
@@ -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 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
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);
@@ -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 @@ import org.springframework.util.CollectionUtils;
* Helper bean for registering tasks with a {@link TaskScheduler}, typically using cron
* expressions.
*
* <p>As of Spring 3.1, {@code ScheduledTaskRegistrar} has a more prominent user-facing
* role when used in conjunction with the {@link
* <p>{@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 class ScheduledTaskRegistrar implements ScheduledTaskHolder, Initializing
@Override
public void destroy() {
for (ScheduledTask task : this.scheduledTasks) {
task.cancel();
task.cancel(false);
}
if (this.localExecutor != null) {
this.localExecutor.shutdownNow();
@@ -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 abstract class AbstractBindingResult extends AbstractErrors implements Bi
}
@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,
@@ -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.lang.Nullable;
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 @@ public abstract class AbstractErrors implements Errors, Serializable {
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 @@ public abstract class AbstractErrors implements Errors, Serializable {
}
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 abstract class AbstractErrors implements Errors, Serializable {
List<FieldError> fieldErrors = getFieldErrors();
List<FieldError> 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);
@@ -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 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
* @param autoGrowNestedPaths whether to "auto-grow" a nested path that contains a null value
@@ -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 class BindException extends Exception implements BindingResult {
}
@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);
}
@@ -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 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
* @param autoGrowNestedPaths whether to "auto-grow" a nested path that contains a null value
@@ -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.beans.PropertyAccessor;
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.
*
* <p>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.
* <p>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.
*
* <p>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.
* <p>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 {
* <p>For example: current path "spouse.", pushNestedPath("child") &rarr;
* result path "spouse.child."; popNestedPath() &rarr; "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 @@ public interface Errors {
* 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<ObjectError> 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 @@ public interface Errors {
/**
* Get all global errors.
* @return a list of {@link ObjectError} instances
* @see #getFieldErrors()
*/
List<ObjectError> getGlobalErrors();
/**
* Get the <i>first</i> 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 @@ public interface Errors {
/**
* Get all errors associated with a field.
* @return a List of {@link FieldError} instances
* @see #getGlobalErrors()
*/
List<FieldError> getFieldErrors();
/**
* Get the <i>first</i> 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.
* <p>Implementations should support not only full field names like
* "name" but also pattern matches like "na*" or "address.*".
* <p>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<FieldError> getFieldErrors(String field);
@@ -279,6 +292,7 @@ public interface Errors {
* 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 @@ public interface Errors {
* 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.
* <p>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);
@@ -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 @@ package org.springframework.validation;
* }
* }</pre>
*
* <p>See also the Spring reference manual for a fuller discussion of
* the {@code Validator} interface and its role in an enterprise
* application.
* <p>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}.
* <p>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
@@ -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 @@ import org.springframework.lang.Nullable;
* 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.
* <p>This implementation checks for {@code @javax.validation.Valid},
* Spring's {@link org.springframework.validation.annotation.Validated},
* and custom annotations whose name starts with "Valid".
* <p>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;
}
@@ -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 @@ class CacheReproTests {
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 @@ class CacheReproTests {
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 @@ class CacheReproTests {
TestBean tb2 = bean.findById("tb1").get();
assertThat(tb2).isNotSameAs(tb);
assertThat(cache.get("tb1").get()).isSameAs(tb2);
context.close();
}
@@ -177,6 +179,7 @@ class CacheReproTests {
bean.insertItem(tb);
assertThat(bean.findById("tb1").get()).isSameAs(tb);
assertThat(cache.get("tb1").get()).isSameAs(tb);
context.close();
}
@@ -190,6 +193,7 @@ class CacheReproTests {
bean.insertItem(tb);
assertThat(bean.findById("tb1").get()).isSameAs(tb);
assertThat(cache.get("tb1").get()).isSameAs(tb);
context.close();
}
@@ -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 class CacheSyncFailureTests {
}
}
@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 class CacheSyncFailureTests {
}
}
@Configuration
@EnableCaching
static class Config implements CachingConfigurer {
@@ -197,6 +197,7 @@ public class ClassPathBeanDefinitionScannerTests {
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 class ClassPathBeanDefinitionScannerTests {
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());
@@ -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.
@@ -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 @@ class AnnotationDrivenEventListenerTests {
ContextEventListener listener = this.context.getBean(ContextEventListener.class);
List<Object> 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<Object> 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 @@ class AnnotationDrivenEventListenerTests {
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 @@ class AnnotationDrivenEventListenerTests {
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 @@ class AnnotationDrivenEventListenerTests {
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 @@ class AnnotationDrivenEventListenerTests {
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 @@ class AnnotationDrivenEventListenerTests {
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 @@ class AnnotationDrivenEventListenerTests {
public void handleContextEvent(ApplicationContextEvent event) {
collectEvent(event);
}
}
@@ -980,7 +989,6 @@ class AnnotationDrivenEventListenerTests {
}
@EventListener
@Retention(RetentionPolicy.RUNTIME)
public @interface ConditionalEvent {
@@ -1032,7 +1040,7 @@ class AnnotationDrivenEventListenerTests {
}
@Configuration
@Component
static class OrderedTestListener extends TestEventListener {
public final List<String> order = new ArrayList<>();
@@ -1056,6 +1064,11 @@ class AnnotationDrivenEventListenerTests {
}
@Component
public static class PublicSubclassWithInheritedEventListener extends TestEventListener {
}
static class EventOnPostConstruct {
@Autowired
@@ -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.
@@ -63,7 +63,7 @@ public class EnableSchedulingTests {
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 class EnableSchedulingTests {
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 class EnableSchedulingTests {
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 class EnableSchedulingTests {
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 class EnableSchedulingTests {
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 class EnableSchedulingTests {
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 class EnableSchedulingTests {
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 class EnableSchedulingTests {
public void withTriggerTask() throws InterruptedException {
ctx = new AnnotationConfigApplicationContext(TriggerTaskConfig.class);
Thread.sleep(100);
Thread.sleep(110);
assertThat(ctx.getBean(AtomicInteger.class).get()).isGreaterThan(1);
}
@@ -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.
@@ -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 @@ class DataBinderTests {
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 @@ class DataBinderTests {
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 @@ class DataBinderTests {
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 @@ class DataBinderTests {
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);
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);
assertThat(BindingResultUtils.getBindingResult(map, "someOtherName")).isNull();
assertThatIllegalStateException().isThrownBy(() ->
BindingResultUtils.getRequiredBindingResult(map, "someOtherName"));
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(binder.getBindingResult()).as("Added itself to map").isSameAs(br);
assertThat(br.hasErrors()).isTrue();
assertThat(br.getErrorCount()).isEqualTo(2);
assertThat(BindingResultUtils.getBindingResult(map, "someOtherName")).isNull();
assertThatIllegalStateException().isThrownBy(() ->
BindingResultUtils.getRequiredBindingResult(map, "someOtherName"));
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(binder.getBindingResult()).as("Added itself to map").isSameAs(br);
assertThat(br.hasErrors()).isTrue();
assertThat(br.getErrorCount()).isEqualTo(2);
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();
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);
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());
});
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 @@ class DataBinderTests {
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 @@ class DataBinderTests {
return ((TestBean) getValue()).getName();
}
});
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("name", "Rod");
pvs.add("age", "32x");
@@ -293,41 +297,39 @@ class DataBinderTests {
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);
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);
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("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("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();
});
assertThat(br.hasFieldErrors("spouse")).isFalse();
assertThat(binder.getBindingResult().getFieldValue("spouse")).isEqualTo("Kerry");
assertThat(tb.getSpouse()).isNotNull();
});
}
@Test
@@ -576,7 +578,7 @@ class DataBinderTests {
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 @@ class DataBinderTests {
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 @@ class DataBinderTests {
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 @@ class DataBinderTests {
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 @@ class DataBinderTests {
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 @@ class DataBinderTests {
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 @@ class DataBinderTests {
errors.popNestedPath();
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
errors.popNestedPath();
assertThat(errors.getNestedPath()).isEqualTo("");
assertThat(errors.getNestedPath()).isEmpty();
try {
errors.popNestedPath();
}
@@ -1166,7 +1167,7 @@ class DataBinderTests {
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 @@ class DataBinderTests {
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 @@ class DataBinderTests {
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 @@ class DataBinderTests {
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 @@ class DataBinderTests {
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 @@ class DataBinderTests {
@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 @@ class DataBinderTests {
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 @@ class DataBinderTests {
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 @@ class DataBinderTests {
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 @@ class DataBinderTests {
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 @@ class DataBinderTests {
List<Object> list = (List<Object>) 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 @@ class DataBinderTests {
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);
}
@@ -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 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
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 class ValidationUtilsTests {
}
@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 class ValidationUtilsTests {
}
@Test
public void testValidationUtilsSunnyDay() throws Exception {
public void testValidationUtilsSunnyDay() {
TestBean tb = new TestBean("");
Validator testValidator = new EmptyValidator();
@@ -75,7 +75,7 @@ public class ValidationUtilsTests {
}
@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 class ValidationUtilsTests {
}
@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 class ValidationUtilsTests {
}
@Test
public void testValidationUtilsEmptyOrWhitespace() throws Exception {
public void testValidationUtilsEmptyOrWhitespace() {
TestBean tb = new TestBean();
Validator testValidator = new EmptyOrWhitespaceValidator();
@@ -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 final class MethodIntrospector {
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);
}
}
@@ -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 abstract class ParameterizedTypeReference<T> {
* @since 4.3.12
*/
public static <T> ParameterizedTypeReference<T> forType(Type type) {
return new ParameterizedTypeReference<T>(type) {
};
return new ParameterizedTypeReference<T>(type) {};
}
private static Class<?> findParameterizedTypeReferenceSubclass(Class<?> child) {
@@ -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.CompletableFuture;
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.ConcurrentReferenceHashMap;
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.
*
* <p>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}.
* <p>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.
*
* <p><strong>Note:</strong> 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 class ReactiveAdapterRegistry {
* 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}.
* <p>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<Object, Publisher<?>> toAdapter, Function<Publisher<?>, 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}.
* <p>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.
* <p>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<Object, Publisher<?>> toAdapter, Function<Publisher<?>, Object> fromAdapter) {
this.adapters.add(0, buildAdapter(descriptor, toAdapter, fromAdapter));
}
private ReactiveAdapter buildAdapter(ReactiveTypeDescriptor descriptor,
Function<Object, Publisher<?>> toAdapter, Function<Publisher<?>, Object> fromAdapter) {
return (reactorPresent ? new ReactorAdapter(descriptor, toAdapter, fromAdapter) :
new ReactiveAdapter(descriptor, toAdapter, fromAdapter));
}
/**
@@ -401,9 +429,9 @@ public class ReactiveAdapterRegistry {
@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(
@@ -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 @@ public final class ReactiveTypeDescriptor {
this.reactiveType = reactiveType;
this.multiValue = multiValue;
this.noValue = noValue;
this.emptyValueSupplier = emptySupplier;
this.emptySupplier = emptySupplier;
this.deferred = deferred;
}
@@ -89,16 +89,16 @@ public final class ReactiveTypeDescriptor {
* 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}.
* <p>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();
}
/**
@@ -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.
@@ -1321,6 +1321,9 @@ public abstract class AnnotationUtils {
public static void clearCache() {
AnnotationTypeMappings.clearCache();
AnnotationsScanner.clearCache();
AttributeMethods.cache.clear();
RepeatableContainers.cache.clear();
OrderUtils.orderCache.clear();
}
@@ -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<Class<? extends Annotation>, AttributeMethods> cache =
new ConcurrentReferenceHashMap<>();
static final Map<Class<? extends Annotation>, AttributeMethods> cache = new ConcurrentReferenceHashMap<>();
private static final Comparator<Method> methodComparator = (m1, m2) -> {
if (m1 != null && m2 != null) {
@@ -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<AnnotatedElement, Object> orderCache = new ConcurrentReferenceHashMap<>(64);
static final Map<AnnotatedElement, Object> orderCache = new ConcurrentReferenceHashMap<>(64);
/**
@@ -43,6 +43,8 @@ import org.springframework.util.ObjectUtils;
*/
public abstract class RepeatableContainers {
static final Map<Class<? extends Annotation>, Object> cache = new ConcurrentReferenceHashMap<>();
@Nullable
private final RepeatableContainers parent;
@@ -137,8 +139,6 @@ public abstract class RepeatableContainers {
*/
private static class StandardRepeatableContainers extends RepeatableContainers {
private static final Map<Class<? extends Annotation>, Object> cache = new ConcurrentReferenceHashMap<>();
private static final Object NONE = new Object();
private static StandardRepeatableContainers INSTANCE = new StandardRepeatableContainers();
@@ -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 class GenericConversionService implements ConfigurableConversionService {
* @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) {
@@ -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 abstract class CommandLinePropertySource<T> extends EnumerablePropertySou
@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 abstract class CommandLinePropertySource<T> extends EnumerablePropertySou
@Nullable
public final String getProperty(String name) {
if (this.nonOptionArgsPropertyName.equals(name)) {
Collection<String> nonOptionArguments = this.getNonOptionArgs();
Collection<String> nonOptionArguments = getNonOptionArgs();
if (nonOptionArguments.isEmpty()) {
return null;
}
@@ -278,7 +278,7 @@ public abstract class CommandLinePropertySource<T> extends EnumerablePropertySou
return StringUtils.collectionToCommaDelimitedString(nonOptionArguments);
}
}
Collection<String> optionValues = this.getOptionValues(name);
Collection<String> optionValues = getOptionValues(name);
if (optionValues == null) {
return null;
}
@@ -168,31 +168,27 @@ final class ProfilesParser {
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 ");
}
}
}
@@ -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 @@ import org.springframework.util.concurrent.ListenableFutureTask;
* {@link TaskExecutor} implementation that fires up a new Thread for each task,
* executing it asynchronously.
*
* <p>Supports limiting concurrent threads through the "concurrencyLimit"
* bean property. By default, the number of concurrent threads is unlimited.
* <p>Supports limiting concurrent threads through {@link #setConcurrencyLimit}.
* By default, the number of concurrent task executions is unlimited.
*
* <p><b>NOTE: This implementation does not reuse threads!</b> Consider a
* thread-pooling TaskExecutor implementation instead, in particular for
@@ -133,33 +133,31 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
* 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.
* <p>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.
* <p>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
@@ -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 @@ final class SimpleAnnotationMetadataReadingVisitor extends ClassVisitor {
return (access & Opcodes.ACC_INTERFACE) != 0;
}
/**
* {@link MergedAnnotation} source.
*/
@@ -182,11 +183,6 @@ final class SimpleAnnotationMetadataReadingVisitor extends ClassVisitor {
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 @@ final class SimpleAnnotationMetadataReadingVisitor extends ClassVisitor {
return this.className.equals(((Source) obj).className);
}
@Override
public int hashCode() {
return this.className.hashCode();
}
@Override
public String toString() {
return this.className;
}
}
}
@@ -50,6 +50,7 @@ import org.springframework.lang.Nullable;
* @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 @@ public abstract class ClassUtils {
* @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 abstract class ClassUtils {
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;
@@ -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.
* <p>In principle, this limit can be changed at runtime,
* although it is generally designed as a config time setting.
* <p>NOTE: Do not switch between -1 and any concrete limit at runtime,
@@ -143,9 +143,10 @@ public abstract class ConcurrencyThrottleSupport implements Serializable {
*/
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();
@@ -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 class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
}
@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);
}
}
@@ -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 class MethodInvoker {
/**
* 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 class MethodInvoker {
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;
}
@@ -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 abstract class ObjectUtils {
* <li>{@code"Optional[<concise-string>]"} if {@code obj} is a non-empty {@code Optional},
* where {@code <concise-string>} is the result of invoking {@link #nullSafeConciseToString}
* on the object contained in the {@code Optional}</li>
* <li>{@code "{}"} if {@code obj} is an empty array or {@link Map}</li>
* <li>{@code "{...}"} if {@code obj} is a non-empty array or {@link Map}</li>
* <li>{@code "[]"} if {@code obj} is an empty {@link Collection}</li>
* <li>{@code "[...]"} if {@code obj} is a non-empty {@link Collection}</li>
* <li>{@code "{}"} if {@code obj} is an empty array</li>
* <li>{@code "{...}"} if {@code obj} is a {@link Map} or a non-empty array</li>
* <li>{@code "[...]"} if {@code obj} is a {@link Collection}</li>
* <li>{@linkplain Class#getName() Class name} if {@code obj} is a {@link Class}</li>
* <li>{@linkplain Charset#name() Charset name} if {@code obj} is a {@link Charset}</li>
* <li>{@linkplain TimeZone#getID() TimeZone ID} if {@code obj} is a {@link TimeZone}</li>
@@ -977,12 +976,11 @@ public abstract class ObjectUtils {
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();
@@ -247,26 +247,26 @@ public abstract class StringUtils {
/**
* Trim <em>all</em> 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 abstract class StringUtils {
* @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();
}
@@ -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 @@ public class NullSafeComparator<T> implements Comparator<T> {
* @param nullsLow whether to treat nulls lower or higher than non-null objects
*/
public NullSafeComparator(Comparator<T> 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 class NullSafeComparator<T> implements Comparator<T> {
@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<T> otherComp = (NullSafeComparator<T>) 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
@@ -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 @@ import static org.assertj.core.api.Assertions.assertThat;
* Unit tests for {@link ReactiveAdapterRegistry}.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
*/
@SuppressWarnings("unchecked")
class ReactiveAdapterRegistryTests {
@@ -52,14 +53,40 @@ class ReactiveAdapterRegistryTests {
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 @@ class ReactiveAdapterRegistryTests {
}
private static class ExtendedExtendedFlux<T> extends ExtendedFlux<T> {
}
@Nested
class Reactor {
@@ -100,7 +131,7 @@ class ReactiveAdapterRegistryTests {
List<Integer> sequence = Arrays.asList(1, 2, 3);
Publisher<Integer> 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<Integer>) target).collectList().block(ONE_SECOND)).isEqualTo(sequence);
}
@@ -108,7 +139,7 @@ class ReactiveAdapterRegistryTests {
void toMono() {
Publisher<Integer> 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<Integer>) target).block(ONE_SECOND)).isEqualTo(Integer.valueOf(1));
}
@@ -116,7 +147,7 @@ class ReactiveAdapterRegistryTests {
void toCompletableFuture() throws Exception {
Publisher<Integer> 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<Integer>) target).get()).isEqualTo(Integer.valueOf(1));
}
@@ -125,7 +156,7 @@ class ReactiveAdapterRegistryTests {
CompletableFuture<Integer> 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<Integer>) target).block(ONE_SECOND)).isEqualTo(Integer.valueOf(1));
}
}
@@ -294,7 +325,7 @@ class ReactiveAdapterRegistryTests {
List<Integer> sequence = Arrays.asList(1, 2, 3);
Publisher<Integer> 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 @@ class ReactiveAdapterRegistryTests {
List<Integer> sequence = Arrays.asList(1, 2, 3);
Publisher<Integer> 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 @@ class ReactiveAdapterRegistryTests {
void toSingle() {
Publisher<Integer> 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<Integer>) target).blockingGet()).isEqualTo(Integer.valueOf(1));
}
@@ -319,7 +350,7 @@ class ReactiveAdapterRegistryTests {
void toCompletable() {
Publisher<Integer> 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 @@ class ReactiveAdapterRegistryTests {
List<Integer> 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<Integer>) target).collectList().block(ONE_SECOND)).isEqualTo(sequence);
}
@@ -337,7 +368,7 @@ class ReactiveAdapterRegistryTests {
List<Integer> 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<Integer>) target).collectList().block(ONE_SECOND)).isEqualTo(sequence);
}
@@ -345,7 +376,7 @@ class ReactiveAdapterRegistryTests {
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<Integer>) target).block(ONE_SECOND)).isEqualTo(Integer.valueOf(1));
}
@@ -353,7 +384,7 @@ class ReactiveAdapterRegistryTests {
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<Void>) target).block(ONE_SECOND);
}
}
@@ -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 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
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));
@@ -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 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*/
class CollectionToCollectionConverterTests {
private GenericConversionService conversionService = new GenericConversionService();
private final GenericConversionService conversionService = new GenericConversionService();
@BeforeEach
void setUp() {
void setup() {
conversionService.addConverter(new CollectionToCollectionConverter(conversionService));
}
@@ -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 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @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));
}
@@ -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 @@ package org.springframework.core.convert.support;
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 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
* 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 @@ class ObjectToObjectConverterTests {
@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 @@ class ObjectToObjectConverterTests {
public Data toData() {
return new Data(this.value);
}
}
static class Text {
private final String value;
@@ -98,9 +103,9 @@ class ObjectToObjectConverterTests {
public Optional<Data> toData() {
return Optional.of(new Data(this.value));
}
}
static class Data {
private final String value;
@@ -115,9 +120,8 @@ class ObjectToObjectConverterTests {
}
public static Optional<Data> valueOf(String string) {
return (string != null) ? Optional.of(new Data(string)) : Optional.empty();
return (string != null ? Optional.of(new Data(string)) : Optional.empty());
}
}
}
@@ -1078,8 +1078,8 @@ class ObjectUtilsTests {
void nullSafeConciseToStringForEmptyCollections() {
List<String> list = Collections.emptyList();
Set<Integer> 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 @@ class ObjectUtilsTests {
@Test
void nullSafeConciseToStringForEmptyMaps() {
Map<String, Object> map = Collections.emptyMap();
assertThat(ObjectUtils.nullSafeConciseToString(map)).isEqualTo("{}");
assertThat(ObjectUtils.nullSafeConciseToString(map)).isEqualTo("{...}");
}
@Test
@@ -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 @@ import org.springframework.core.env.PropertySource;
*
* The {@link #setProperty} and {@link #withProperty} methods are exposed for
* convenience, for example:
* <pre>
* <pre class="code">
* {@code
* PropertySource<?> source = new MockPropertySource().withProperty("foo", "bar");
* }
@@ -77,7 +77,7 @@ public class MockPropertySource extends PropertiesPropertySource {
/**
* 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 class MockPropertySource extends PropertiesPropertySource {
* @return this {@link MockPropertySource} instance
*/
public MockPropertySource withProperty(String name, Object value) {
this.setProperty(name, value);
setProperty(name, value);
return this;
}
@@ -77,7 +77,6 @@ import org.springframework.expression.spel.ast.Ternary;
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 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
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 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
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 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
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 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
// 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 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
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 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
return pop();
}
if (peekToken() == null) {
// unexpectedly ran out of data
throw internalException(t.startPos, SpelMessage.OOD);
}
else {
@@ -460,8 +463,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
private void eatConstructorArgs(List<SpelNodeImpl> 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 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
*/
private void consumeArguments(List<SpelNodeImpl> 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 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
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 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
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 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
//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 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
// 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 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
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 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
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 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
//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;
@@ -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 @@ class Token {
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 @@ class Token {
@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();
}
}
@@ -39,7 +39,9 @@ import static org.springframework.expression.spel.SpelMessage.NON_TERMINATING_DO
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 @@ class SpelParserTests {
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 @@ class SpelParserTests {
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 @@ class SpelParserTests {
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<SpelParseException> assertParseExceptionThrownBy(ThrowingCallable throwingCallable) {
@@ -386,15 +394,18 @@ class SpelParserTests {
private static void assertParseException(ThrowingCallable throwingCallable, SpelMessage expectedMessage, int expectedPosition) {
assertParseExceptionThrownBy(throwingCallable)
.satisfies(parseExceptionRequirements(expectedMessage, expectedPosition));
.satisfies(parseExceptionRequirements(expectedMessage, expectedPosition));
}
private static <E extends SpelParseException> Consumer<E> 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());
}
};
}
@@ -77,7 +77,25 @@ public abstract class LogFactory {
*/
@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 abstract class LogFactory {
// 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) {
@@ -74,4 +74,8 @@ public class LogFactoryService extends LogFactory {
return this.attributes.keySet().toArray(new String[0]);
}
@Override
public void release() {
}
}
@@ -343,7 +343,7 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
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(
@@ -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 @@ import org.springframework.lang.Nullable;
* <p>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 <em>Spring
* TestContext Framework</em>, in the {@code spring-test} artifact.
* consider the powerful integration testing support provided via the
* <em>Spring TestContext Framework</em>, in the {@code spring-test} artifact.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see JdbcTemplate
* @see org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations
*/
public interface JdbcOperations {
@@ -65,8 +65,7 @@ import org.springframework.util.StringUtils;
* 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.
*
* <p>Code using this class need only implement callback interfaces, giving
* them a clearly defined contract. The {@link PreparedStatementCreator} callback
@@ -75,7 +74,8 @@ import org.springframework.util.StringUtils;
* values from a ResultSet. See also {@link PreparedStatementSetter} and
* {@link RowMapper} for two popular alternative callback interfaces.
*
* <p>Can be used within a service implementation via direct instantiation
* <p>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 @@ import org.springframework.util.StringUtils;
* <p>All SQL operations performed by this class are logged at debug level,
* using "org.springframework.jdbc.core.JdbcTemplate" as log category.
*
* <p><b>NOTE: An instance of this class is thread-safe once configured.</b>
*
* @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 @@ import org.springframework.util.StringUtils;
* @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 class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
logger.debug("Executing SQL query [" + sql + "]");
}
/**
* Callback to execute the query.
*/
// Callback to execute the query.
class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
@Override
@Nullable
@@ -542,9 +540,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
logger.debug("Executing SQL update [" + sql + "]");
}
/**
* Callback to execute the update statement.
*/
// Callback to execute the update statement.
class UpdateStatementCallback implements StatementCallback<Integer>, SqlProvider {
@Override
public Integer doInStatement(Statement stmt) throws SQLException {
@@ -570,9 +566,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
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<int[]>, SqlProvider {
@Nullable
@@ -1376,7 +1370,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
}
}
}
if (!(param.isResultsParameter())) {
if (!param.isResultsParameter()) {
sqlColIndex++;
}
}
@@ -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 @@ import org.springframework.util.StringUtils;
/**
* {@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.
*
* <p>Uses a Spring BeanWrapper for bean property access underneath.
* <p>Uses a Spring {@link BeanWrapper} for bean property access underneath.
*
* @author Thomas Risberg
* @author Juergen Hoeller
@@ -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 @@ import org.springframework.util.ConcurrentLruCache;
* done at execution time. It also allows for expanding a {@link java.util.List}
* of values to the appropriate number of placeholders.
*
* <p>The underlying {@link org.springframework.jdbc.core.JdbcTemplate} is
* <p>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.
*
* <p><b>NOTE: An instance of this class is thread-safe once configured.</b>
*
* @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 {
@@ -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 @@ import org.springframework.util.StringUtils;
* Abstract class to provide base functionality for easy stored procedure calls
* based on configuration options and database meta-data.
*
* <p>This class provides the base SPI for {@link SimpleJdbcCall}.
* <p>This class provides the processing arrangement for {@link SimpleJdbcCall}.
*
* @author Thomas Risberg
* @author Juergen Hoeller
@@ -453,7 +453,7 @@ public abstract class AbstractJdbcCall {
/**
* 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<String, ?> matchInParameterValuesWithCallParameters(Map<String, ?> args) {
@@ -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.lang.Nullable;
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.
*
* <p>This class provides the base SPI for {@link SimpleJdbcInsert}.
* <p>This class provides the processing arrangement for {@link SimpleJdbcInsert}.
*
* @author Thomas Risberg
* @author Juergen Hoeller
@@ -409,7 +409,7 @@ public abstract class AbstractJdbcInsert {
/**
* 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 @@ public abstract class AbstractJdbcInsert {
/**
* 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 @@ public abstract class AbstractJdbcInsert {
}
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 @@ public abstract class AbstractJdbcInsert {
/**
* 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<String, ?>... batch) {
@@ -549,9 +549,10 @@ public abstract class AbstractJdbcInsert {
}
/**
* 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 @@ public abstract class AbstractJdbcInsert {
* 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<Object> matchInParameterValuesWithInsertColumns(SqlParameterSource parameterSource) {
return this.tableMetaDataContext.matchInParameterValuesWithInsertColumns(parameterSource);
@@ -615,8 +616,8 @@ public abstract class AbstractJdbcInsert {
/**
* 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<Object> matchInParameterValuesWithInsertColumns(Map<String, ?> args) {
return this.tableMetaDataContext.matchInParameterValuesWithInsertColumns(args);
@@ -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 @@ import org.springframework.jdbc.core.namedparam.SqlParameterSource;
* any meta-data processing if you want to use parameter names that do not
* match what is declared during the stored procedure compilation.
*
* <p>The actual insert is being handled using Spring's {@link JdbcTemplate}.
* <p>The actual call is being handled using Spring's {@link JdbcTemplate}.
*
* <p>Many of the configuration methods return the current instance of the
* SimpleJdbcCall in order to provide the ability to chain multiple ones
@@ -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.core.namedparam.SqlParameterSource;
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.
*
* <p>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.
*
* <p>The actual insert is handled using Spring's {@link JdbcTemplate}.
* <p>The actual (batch) insert is handled using Spring's {@link JdbcTemplate}.
*
* <p>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 class SimpleJdbcInsert extends AbstractJdbcInsert implements SimpleJdbcIn
return doExecuteAndReturnKeyHolder(parameterSource);
}
@Override
@SuppressWarnings("unchecked")
@Override
public int[] executeBatch(Map<String, ?>... batch) {
return doExecuteBatch(batch);
}
@@ -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 @@ import org.springframework.util.Assert;
*
* @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 abstract class AbstractFallbackSQLExceptionTranslator implements SQLExcep
/**
* Return the fallback exception translator, if any.
* @see #setFallbackTranslator
*/
@Nullable
public SQLExceptionTranslator getFallbackTranslator() {
@@ -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> sqlErrorCodes;
@@ -194,9 +192,9 @@ public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExcep
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 @@ public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExcep
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;
}
}
}
@@ -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 @@ class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
BeanPropertyRowMapper<ExtendedPerson> 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 @@ class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
BeanPropertyRowMapper<Person> 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

Some files were not shown because too many files have changed in this diff Show More