From 6183f0684684912802021556dce916ba26228c26 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sat, 15 Jul 2023 14:17:52 +0200 Subject: [PATCH 1/2] Cache DependencyDescriptor per autowired constructor argument Aligned with shortcut handling in AutowiredAnnotationBeanPostProcessor. Includes minor MethodInvoker optimization for pre-resolved targetClass. Closes gh-30883 --- .../AutowiredAnnotationBeanPostProcessor.java | 24 ++- .../factory/aot/AutowiredElementResolver.java | 12 +- .../aot/AutowiredFieldValueResolver.java | 5 +- .../aot/AutowiredMethodArgumentsResolver.java | 39 ++--- .../factory/aot/BeanInstanceSupplier.java | 12 +- .../AbstractAutowireCapableBeanFactory.java | 2 +- .../support/BeanDefinitionValueResolver.java | 2 +- .../factory/support/ConstructorResolver.java | 157 +++++++++++++----- .../beans/factory/support/RegisteredBean.java | 10 +- ...wiredAnnotationBeanPostProcessorTests.java | 150 ++++++++++++++++- .../springframework/util/MethodInvoker.java | 12 +- 11 files changed, 310 insertions(+), 115 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index c948f9438d2..da11e0203f5 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -705,7 +705,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 autowiredBeanNames = new LinkedHashSet<>(1); + Set autowiredBeanNames = new LinkedHashSet<>(2); Assert.state(beanFactory != null, "No BeanFactory available"); TypeConverter typeConverter = beanFactory.getTypeConverter(); Object value; @@ -724,8 +724,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; @@ -805,7 +804,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA int argumentCount = method.getParameterCount(); Object[] arguments = new Object[argumentCount]; DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount]; - Set autowiredBeans = new LinkedHashSet<>(argumentCount); + Set autowiredBeanNames = new LinkedHashSet<>(argumentCount * 2); Assert.state(beanFactory != null, "No BeanFactory available"); TypeConverter typeConverter = beanFactory.getTypeConverter(); for (int i = 0; i < arguments.length; i++) { @@ -814,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; @@ -829,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 it = autowiredBeans.iterator(); + registerDependentBeans(beanName, autowiredBeanNames); + if (autowiredBeanNames.size() == argumentCount) { + Iterator it = autowiredBeanNames.iterator(); Class[] paramTypes = method.getParameterTypes(); for (int i = 0; i < paramTypes.length; i++) { String autowiredBeanName = it.next(); if (arguments[i] != null && beanFactory.containsBean(autowiredBeanName) && beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) { cachedMethodArguments[i] = new ShortcutDependencyDescriptor( - descriptors[i], autowiredBeanName, paramTypes[i]); + descriptors[i], autowiredBeanName); } } } @@ -864,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()); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredElementResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredElementResolver.java index 1cd16590837..9fdfce349d3 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredElementResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredElementResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,20 +62,14 @@ abstract class AutowiredElementResolver { private final String shortcut; - private final Class requiredType; - - - public ShortcutDependencyDescriptor(DependencyDescriptor original, - String shortcut, Class requiredType) { + public ShortcutDependencyDescriptor(DependencyDescriptor original, String shortcut) { super(original); this.shortcut = shortcut; - this.requiredType = requiredType; } - @Override public Object resolveShortcut(BeanFactory beanFactory) { - return beanFactory.getBean(this.shortcut, this.requiredType); + return beanFactory.getBean(this.shortcut, getDependencyType()); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredFieldValueResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredFieldValueResolver.java index 8237ccab4ed..12cfc76b083 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredFieldValueResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredFieldValueResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -179,8 +179,7 @@ public final class AutowiredFieldValueResolver extends AutowiredElementResolver DependencyDescriptor descriptor = new DependencyDescriptor(field, this.required); descriptor.setContainingClass(beanClass); if (this.shortcut != null) { - descriptor = new ShortcutDependencyDescriptor(descriptor, this.shortcut, - field.getType()); + descriptor = new ShortcutDependencyDescriptor(descriptor, this.shortcut); } Set autowiredBeanNames = new LinkedHashSet<>(1); TypeConverter typeConverter = beanFactory.getTypeConverter(); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredMethodArgumentsResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredMethodArgumentsResolver.java index 7b878e6fc8f..9c79f232114 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredMethodArgumentsResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AutowiredMethodArgumentsResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,6 +76,7 @@ public final class AutowiredMethodArgumentsResolver extends AutowiredElementReso this.shortcuts = shortcuts; } + /** * Create a new {@link AutowiredMethodArgumentsResolver} for the specified * method where injection is optional. @@ -83,11 +84,8 @@ public final class AutowiredMethodArgumentsResolver extends AutowiredElementReso * @param parameterTypes the factory method parameter types * @return a new {@link AutowiredFieldValueResolver} instance */ - public static AutowiredMethodArgumentsResolver forMethod(String methodName, - Class... parameterTypes) { - - return new AutowiredMethodArgumentsResolver(methodName, parameterTypes, false, - null); + public static AutowiredMethodArgumentsResolver forMethod(String methodName, Class... parameterTypes) { + return new AutowiredMethodArgumentsResolver(methodName, parameterTypes, false, null); } /** @@ -97,11 +95,8 @@ public final class AutowiredMethodArgumentsResolver extends AutowiredElementReso * @param parameterTypes the factory method parameter types * @return a new {@link AutowiredFieldValueResolver} instance */ - public static AutowiredMethodArgumentsResolver forRequiredMethod(String methodName, - Class... parameterTypes) { - - return new AutowiredMethodArgumentsResolver(methodName, parameterTypes, true, - null); + public static AutowiredMethodArgumentsResolver forRequiredMethod(String methodName, Class... parameterTypes) { + return new AutowiredMethodArgumentsResolver(methodName, parameterTypes, true, null); } /** @@ -113,8 +108,7 @@ public final class AutowiredMethodArgumentsResolver extends AutowiredElementReso * the shortcuts */ public AutowiredMethodArgumentsResolver withShortcut(String... beanNames) { - return new AutowiredMethodArgumentsResolver(this.methodName, this.parameterTypes, - this.required, beanNames); + return new AutowiredMethodArgumentsResolver(this.methodName, this.parameterTypes, this.required, beanNames); } /** @@ -123,9 +117,7 @@ public final class AutowiredMethodArgumentsResolver extends AutowiredElementReso * @param registeredBean the registered bean * @param action the action to execute with the resolved method arguments */ - public void resolve(RegisteredBean registeredBean, - ThrowingConsumer action) { - + public void resolve(RegisteredBean registeredBean, ThrowingConsumer action) { Assert.notNull(registeredBean, "'registeredBean' must not be null"); Assert.notNull(action, "'action' must not be null"); AutowiredArguments resolved = resolve(registeredBean); @@ -177,25 +169,22 @@ public final class AutowiredMethodArgumentsResolver extends AutowiredElementReso TypeConverter typeConverter = beanFactory.getTypeConverter(); for (int i = 0; i < argumentCount; i++) { MethodParameter parameter = new MethodParameter(method, i); - DependencyDescriptor descriptor = new DependencyDescriptor(parameter, - this.required); + DependencyDescriptor descriptor = new DependencyDescriptor(parameter, this.required); descriptor.setContainingClass(beanClass); - String shortcut = (this.shortcuts != null) ? this.shortcuts[i] : null; + String shortcut = (this.shortcuts != null ? this.shortcuts[i] : null); if (shortcut != null) { - descriptor = new ShortcutDependencyDescriptor(descriptor, shortcut, - parameter.getParameterType()); + descriptor = new ShortcutDependencyDescriptor(descriptor, shortcut); } try { - Object argument = autowireCapableBeanFactory.resolveDependency(descriptor, - beanName, autowiredBeanNames, typeConverter); + Object argument = autowireCapableBeanFactory.resolveDependency( + descriptor, beanName, autowiredBeanNames, typeConverter); if (argument == null && !this.required) { return null; } arguments[i] = argument; } catch (BeansException ex) { - throw new UnsatisfiedDependencyException(null, beanName, - new InjectionPoint(parameter), ex); + throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(parameter), ex); } } registerDependentBeans(beanFactory, beanName, autowiredBeanNames); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java index 291cbba4f2b..265e939e01c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java @@ -248,18 +248,18 @@ public final class BeanInstanceSupplier extends AutowiredElementResolver impl () -> "'shortcuts' must contain " + resolved.length + " elements"); ConstructorArgumentValues argumentValues = resolveArgumentValues(registeredBean); - Set autowiredBeans = new LinkedHashSet<>(resolved.length); + Set autowiredBeanNames = new LinkedHashSet<>(resolved.length * 2); for (int i = startIndex; i < parameterCount; i++) { MethodParameter parameter = getMethodParameter(executable, i); DependencyDescriptor descriptor = new DependencyDescriptor(parameter, true); String shortcut = (this.shortcuts != null ? this.shortcuts[i - startIndex] : null); if (shortcut != null) { - descriptor = new ShortcutDependencyDescriptor(descriptor, shortcut, registeredBean.getBeanClass()); + descriptor = new ShortcutDependencyDescriptor(descriptor, shortcut); } ValueHolder argumentValue = argumentValues.getIndexedArgumentValue(i, null); - resolved[i - startIndex] = resolveArgument(registeredBean, descriptor, argumentValue, autowiredBeans); + resolved[i - startIndex] = resolveArgument(registeredBean, descriptor, argumentValue, autowiredBeanNames); } - registerDependentBeans(registeredBean.getBeanFactory(), registeredBean.getBeanName(), autowiredBeans); + registerDependentBeans(registeredBean.getBeanFactory(), registeredBean.getBeanName(), autowiredBeanNames); return AutowiredArguments.of(resolved); } @@ -302,7 +302,7 @@ public final class BeanInstanceSupplier extends AutowiredElementResolver impl @Nullable private Object resolveArgument(RegisteredBean registeredBean, DependencyDescriptor descriptor, - @Nullable ValueHolder argumentValue, Set autowiredBeans) { + @Nullable ValueHolder argumentValue, Set autowiredBeanNames) { TypeConverter typeConverter = registeredBean.getBeanFactory().getTypeConverter(); if (argumentValue != null) { @@ -311,7 +311,7 @@ public final class BeanInstanceSupplier extends AutowiredElementResolver impl descriptor.getDependencyType(), descriptor.getMethodParameter())); } try { - return registeredBean.resolveAutowiredArgument(descriptor, typeConverter, autowiredBeans); + return registeredBean.resolveAutowiredArgument(descriptor, typeConverter, autowiredBeanNames); } catch (BeansException ex) { throw new UnsatisfiedDependencyException(null, registeredBean.getBeanName(), descriptor, ex); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index ae8290d1512..8f03d2f605a 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -1483,8 +1483,8 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac converter = bw; } - Set autowiredBeanNames = new LinkedHashSet<>(4); String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw); + Set autowiredBeanNames = new LinkedHashSet<>(propertyNames.length * 2); for (String propertyName : propertyNames) { try { PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java index 62a660a873b..11ee6eef854 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java @@ -153,7 +153,7 @@ public class BeanDefinitionValueResolver { (name, mbd) -> resolveInnerBeanValue(argName, name, mbd)); } else if (value instanceof DependencyDescriptor dependencyDescriptor) { - Set autowiredBeanNames = new LinkedHashSet<>(4); + Set autowiredBeanNames = new LinkedHashSet<>(2); Object result = this.beanFactory.resolveDependency( dependencyDescriptor, this.beanName, autowiredBeanNames, this.typeConverter); for (String autowiredBeanName : autowiredBeanNames) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java index 878e6c2cb46..2197968de58 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java @@ -48,6 +48,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.FactoryBean; import org.springframework.beans.factory.InjectionPoint; import org.springframework.beans.factory.NoSuchBeanDefinitionException; @@ -85,7 +86,7 @@ import org.springframework.util.StringUtils; * @author Sebastien Deleuze * @author Sam Brannen * @author Stephane Nicoll - * @author Phil Webb + * @author Phillip Webb * @since 2.0 * @see #autowireConstructor * @see #instantiateUsingFactoryMethod @@ -96,12 +97,6 @@ class ConstructorResolver { private static final Object[] EMPTY_ARGS = new Object[0]; - /** - * Marker for autowired arguments in a cached argument array, to be replaced - * by a {@linkplain #resolveAutowiredArgument resolved autowired argument}. - */ - private static final Object autowiredArgumentMarker = new Object(); - private static final NamedThreadLocal currentInjectionPoint = new NamedThreadLocal<>("Current injection point"); @@ -729,7 +724,7 @@ class ConstructorResolver { ArgumentsHolder args = new ArgumentsHolder(paramTypes.length); Set usedValueHolders = new HashSet<>(paramTypes.length); - Set autowiredBeanNames = new LinkedHashSet<>(4); + Set allAutowiredBeanNames = new LinkedHashSet<>(paramTypes.length * 2); for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) { Class paramType = paramTypes[paramIndex]; @@ -764,8 +759,8 @@ 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 constructorValueHolder) { @@ -788,11 +783,17 @@ class ConstructorResolver { "] - did you specify the correct bean references as arguments?"); } try { - Object autowiredArgument = resolveAutowiredArgument(new DependencyDescriptor(methodParam, true), - beanName, autowiredBeanNames, converter, fallback); - args.rawArguments[paramIndex] = autowiredArgument; - args.arguments[paramIndex] = autowiredArgument; - args.preparedArguments[paramIndex] = autowiredArgumentMarker; + ConstructorDependencyDescriptor desc = new ConstructorDependencyDescriptor(methodParam, true); + Set autowiredBeanNames = new LinkedHashSet<>(2); + Object arg = resolveAutowiredArgument( + desc, paramType, beanName, autowiredBeanNames, converter, fallback); + if (arg != null) { + setShortcutIfPossible(desc, paramType, autowiredBeanNames); + } + allAutowiredBeanNames.addAll(autowiredBeanNames); + args.rawArguments[paramIndex] = arg; + args.arguments[paramIndex] = arg; + args.preparedArguments[paramIndex] = desc; args.resolveNecessary = true; } catch (BeansException ex) { @@ -802,14 +803,7 @@ 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,32 +823,56 @@ 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(new DependencyDescriptor(methodParam, true), - beanName, null, converter, true); + Class paramType = paramTypes[argIndex]; + boolean convertNecessary = false; + if (argValue instanceof ConstructorDependencyDescriptor descriptor) { + try { + argValue = resolveAutowiredArgument(descriptor, paramType, beanName, + null, converter, true); + } + catch (BeansException ex) { + // Unexpected target bean mismatch for cached argument -> re-resolve + synchronized (descriptor) { + if (!descriptor.hasShortcut()) { + throw ex; + } + descriptor.setShortcut(null); + Set autowiredBeanNames = new LinkedHashSet<>(2); + argValue = resolveAutowiredArgument(descriptor, paramType, beanName, + autowiredBeanNames, converter, true); + 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 text) { argValue = this.beanFactory.evaluateBeanDefinitionString(text, 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) { @@ -870,13 +888,12 @@ 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(DependencyDescriptor descriptor, String beanName, + Object resolveAutowiredArgument(DependencyDescriptor descriptor, Class paramType, String beanName, @Nullable Set autowiredBeanNames, TypeConverter typeConverter, boolean fallback) { - Class paramType = descriptor.getMethodParameter().getParameterType(); if (InjectionPoint.class.isAssignableFrom(paramType)) { InjectionPoint injectionPoint = currentInjectionPoint.get(); if (injectionPoint == null) { @@ -884,9 +901,9 @@ class ConstructorResolver { } return injectionPoint; } + try { - return this.beanFactory.resolveDependency( - descriptor, beanName, autowiredBeanNames, typeConverter); + return this.beanFactory.resolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter); } catch (NoUniqueBeanDefinitionException ex) { throw ex; @@ -909,6 +926,31 @@ class ConstructorResolver { } } + private void setShortcutIfPossible( + ConstructorDependencyDescriptor descriptor, Class paramType, Set autowiredBeanNames) { + + if (autowiredBeanNames.size() == 1) { + String autowiredBeanName = autowiredBeanNames.iterator().next(); + if (this.beanFactory.containsBean(autowiredBeanName) && + this.beanFactory.isTypeMatch(autowiredBeanName, paramType)) { + descriptor.setShortcut(autowiredBeanName); + } + } + } + + private void registerDependentBeans( + Executable executable, String beanName, Set autowiredBeanNames) { + + for (String autowiredBeanName : autowiredBeanNames) { + this.beanFactory.registerDependentBean(autowiredBeanName, beanName); + if (logger.isDebugEnabled()) { + logger.debug("Autowiring by type from bean name '" + beanName + "' via " + + (executable instanceof Constructor ? "constructor" : "factory method") + + " to bean named '" + autowiredBeanName + "'"); + } + } + } + // AOT-oriented pre-resolution @@ -1361,6 +1403,37 @@ 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); + } + } + + private enum FallbackMode { NONE, diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RegisteredBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RegisteredBean.java index c31cd22c714..8b80359352d 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/RegisteredBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RegisteredBean.java @@ -216,16 +216,18 @@ public final class RegisteredBean { * Resolve an autowired argument. * @param descriptor the descriptor for the dependency (field/method/constructor) * @param typeConverter the TypeConverter to use for populating arrays and collections - * @param autowiredBeans a Set that all names of autowired beans (used for + * @param autowiredBeanNames a Set that all names of autowired beans (used for * resolving the given dependency) are supposed to be added to * @return the resolved object, or {@code null} if none found * @since 6.0.9 */ @Nullable - public Object resolveAutowiredArgument(DependencyDescriptor descriptor, TypeConverter typeConverter, - Set autowiredBeans) { + public Object resolveAutowiredArgument( + DependencyDescriptor descriptor, TypeConverter typeConverter, Set autowiredBeanNames) { + return new ConstructorResolver((AbstractAutowireCapableBeanFactory) getBeanFactory()) - .resolveAutowiredArgument(descriptor, getBeanName(), autowiredBeans, typeConverter, true); + .resolveAutowiredArgument(descriptor, descriptor.getDependencyType(), + getBeanName(), autowiredBeanNames, typeConverter, true); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index 1368f39fb77..4174aa48ecf 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -71,6 +71,8 @@ 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.ObjectUtils; import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -128,6 +130,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 @@ -150,10 +154,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); @@ -168,6 +174,18 @@ 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(); @@ -186,12 +204,43 @@ public class AutowiredAnnotationBeanPostProcessorTests { assertThat(bean.getTestBean2()).isNull(); assertThat(bean.getTestBean3()).isNull(); + assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"}); + } + + @Test + void resourceInjectionWithSometimesNullBeanLate() { + RootBeanDefinition bd = new RootBeanDefinition(OptionalResourceInjectionBean.class); + bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + RootBeanDefinition tb = new RootBeanDefinition(SometimesNullFactoryMethods.class); + tb.setFactoryMethodName("createTestBean"); + tb.setScope(BeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("testBean", tb); + + SometimesNullFactoryMethods.active = true; + OptionalResourceInjectionBean bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isNotNull(); + assertThat(bean.getTestBean2()).isNotNull(); + assertThat(bean.getTestBean3()).isNotNull(); + SometimesNullFactoryMethods.active = true; bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean()).isNotNull(); assertThat(bean.getTestBean2()).isNotNull(); assertThat(bean.getTestBean3()).isNotNull(); + SometimesNullFactoryMethods.active = false; + bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isNull(); + assertThat(bean.getTestBean2()).isNull(); + assertThat(bean.getTestBean3()).isNull(); + + SometimesNullFactoryMethods.active = false; + bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isNull(); + assertThat(bean.getTestBean2()).isNull(); + assertThat(bean.getTestBean3()).isNull(); + SometimesNullFactoryMethods.active = true; bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean()).isNotNull(); @@ -203,6 +252,8 @@ public class AutowiredAnnotationBeanPostProcessorTests { assertThat(bean.getTestBean()).isNull(); assertThat(bean.getTestBean2()).isNull(); assertThat(bean.getTestBean3()).isNull(); + + assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"}); } @Test @@ -231,10 +282,7 @@ public class AutowiredAnnotationBeanPostProcessorTests { assertThat(bean.getNestedTestBean()).isSameAs(ntb); assertThat(bean.getBeanFactory()).isSameAs(bf); - String[] depBeans = bf.getDependenciesForBean("annotatedBean"); - assertThat(depBeans).hasSize(2); - assertThat(depBeans[0]).isEqualTo("testBean"); - assertThat(depBeans[1]).isEqualTo("nestedTestBean"); + assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean", "nestedTestBean"}); } @Test @@ -696,6 +744,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 @@ -858,6 +909,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 void constructorResourceInjectionWithCollectionAndNullFromFactoryBean() { bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition( @@ -2788,6 +2913,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; diff --git a/spring-core/src/main/java/org/springframework/util/MethodInvoker.java b/spring-core/src/main/java/org/springframework/util/MethodInvoker.java index b3e0c555480..0443c5699ca 100644 --- a/spring-core/src/main/java/org/springframework/util/MethodInvoker.java +++ b/spring-core/src/main/java/org/springframework/util/MethodInvoker.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -123,8 +123,8 @@ public 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; } From 3a278cc66d428dcc42faa86830cc9b7b8b08c8e3 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sat, 15 Jul 2023 14:20:00 +0200 Subject: [PATCH 2/2] Polishing --- .../support/ByteBufferConverterTests.java | 29 +++++++------- .../CollectionToCollectionConverterTests.java | 4 +- .../support/MapToMapConverterTests.java | 4 +- .../support/ObjectToObjectConverterTests.java | 38 ++++++++++--------- 4 files changed, 39 insertions(+), 36 deletions(-) diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/ByteBufferConverterTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/ByteBufferConverterTests.java index 9c0f46ecb43..c1bcf11551b 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/ByteBufferConverterTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/ByteBufferConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,57 +33,56 @@ 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)); diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java index 2b94fde5170..6d0f1751d90 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java @@ -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)); } diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java index e8d4e74f54f..09428a6c3e5 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java @@ -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)); } diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/ObjectToObjectConverterTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/ObjectToObjectConverterTests.java index 7565d74992a..4af44b77f4f 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/ObjectToObjectConverterTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/ObjectToObjectConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ 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 toData() { return Optional.of(new Data(this.value)); } - } + static class Data { private final String value; @@ -115,9 +120,8 @@ class ObjectToObjectConverterTests { } public static Optional valueOf(String string) { - return (string != null) ? Optional.of(new Data(string)) : Optional.empty(); + return (string != null ? Optional.of(new Data(string)) : Optional.empty()); } - } }