Support primitive types in BeanUtils.instantiateClass args
This commit adds support for primitive default values for BeanUtils.instantiateClass arguments in Java in order to provide a consistent behavior in both Java and Kotlin languages. Closes gh-22531
This commit is contained in:
parent
256f8549d0
commit
aabfc5f0a1
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -70,6 +70,18 @@ public abstract class BeanUtils {
|
|||
private static final Set<Class<?>> unknownEditorTypes =
|
||||
Collections.newSetFromMap(new ConcurrentReferenceHashMap<>(64));
|
||||
|
||||
private static final Map<Class<?>, Object> DEFAULT_TYPE_VALUES;
|
||||
|
||||
static {
|
||||
Map<Class<?>, Object> values = new HashMap<>();
|
||||
values.put(boolean.class, false);
|
||||
values.put(byte.class, (byte) 0);
|
||||
values.put(short.class, (short) 0);
|
||||
values.put(int.class, 0);
|
||||
values.put(long.class, (long) 0);
|
||||
DEFAULT_TYPE_VALUES = Collections.unmodifiableMap(values);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convenience method to instantiate a class using its no-arg constructor.
|
||||
|
@ -159,7 +171,7 @@ public abstract class BeanUtils {
|
|||
* with optional parameters and default values.
|
||||
* @param ctor the constructor to instantiate
|
||||
* @param args the constructor arguments to apply (use {@code null} for an unspecified
|
||||
* parameter if needed for Kotlin classes with optional parameters and default values)
|
||||
* parameter, Kotlin optional parameters and Java primitive types are supported)
|
||||
* @return the new instance
|
||||
* @throws BeanInstantiationException if the bean cannot be instantiated
|
||||
* @see Constructor#newInstance
|
||||
|
@ -168,8 +180,24 @@ public abstract class BeanUtils {
|
|||
Assert.notNull(ctor, "Constructor must not be null");
|
||||
try {
|
||||
ReflectionUtils.makeAccessible(ctor);
|
||||
return (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass()) ?
|
||||
KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args));
|
||||
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
|
||||
return KotlinDelegate.instantiateClass(ctor, args);
|
||||
}
|
||||
else {
|
||||
Class<?>[] parameterTypes = ctor.getParameterTypes();
|
||||
Assert.isTrue(args.length <= parameterTypes.length, "Can't specify more arguments than constructor parameters");
|
||||
Object[] argsWithDefaultValues = new Object[args.length];
|
||||
for (int i = 0 ; i < args.length; i++) {
|
||||
if (args[i] == null) {
|
||||
Class<?> parameterType = parameterTypes[i];
|
||||
argsWithDefaultValues[i] = (parameterType.isPrimitive() ? DEFAULT_TYPE_VALUES.get(parameterType) : null);
|
||||
}
|
||||
else {
|
||||
argsWithDefaultValues[i] = args[i];
|
||||
}
|
||||
}
|
||||
return ctor.newInstance(argsWithDefaultValues);
|
||||
}
|
||||
}
|
||||
catch (InstantiationException ex) {
|
||||
throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 the original author 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.beans;
|
|||
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -28,6 +29,7 @@ import org.springframework.beans.factory.BeanFactory;
|
|||
import org.springframework.beans.propertyeditors.CustomDateEditor;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceEditor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.tests.sample.beans.DerivedTestBean;
|
||||
import org.springframework.tests.sample.beans.ITestBean;
|
||||
import org.springframework.tests.sample.beans.TestBean;
|
||||
|
@ -40,6 +42,7 @@ import static org.junit.Assert.*;
|
|||
* @author Juergen Hoeller
|
||||
* @author Rob Harrop
|
||||
* @author Chris Beams
|
||||
* @author Sebastien Deleuze
|
||||
* @since 19.05.2003
|
||||
*/
|
||||
public class BeanUtilsTests {
|
||||
|
@ -68,6 +71,31 @@ public class BeanUtilsTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test // gh-22531
|
||||
public void testInstantiateClassWithOptionalNullableType() throws NoSuchMethodException {
|
||||
Constructor<BeanWithNullableTypes> ctor = BeanWithNullableTypes.class.getDeclaredConstructor(
|
||||
Integer.class, Boolean.class, String.class);
|
||||
BeanWithNullableTypes bean = BeanUtils.instantiateClass(ctor, null, null, "foo");
|
||||
assertNull(bean.getCounter());
|
||||
assertNull(bean.isFlag());
|
||||
assertEquals("foo", bean.getValue());
|
||||
}
|
||||
|
||||
@Test // gh-22531
|
||||
public void testInstantiateClassWithOptionalPrimitiveType() throws NoSuchMethodException {
|
||||
Constructor<BeanWithPrimitiveTypes> ctor = BeanWithPrimitiveTypes.class.getDeclaredConstructor(int.class, boolean.class, String.class);
|
||||
BeanWithPrimitiveTypes bean = BeanUtils.instantiateClass(ctor, null, null, "foo");
|
||||
assertEquals(0, bean.getCounter());
|
||||
assertEquals(false, bean.isFlag());
|
||||
assertEquals("foo", bean.getValue());
|
||||
}
|
||||
|
||||
@Test(expected = BeanInstantiationException.class) // gh-22531
|
||||
public void testInstantiateClassWithMoreArgsThanParameters() throws NoSuchMethodException {
|
||||
Constructor<BeanWithPrimitiveTypes> ctor = BeanWithPrimitiveTypes.class.getDeclaredConstructor(int.class, boolean.class, String.class);
|
||||
BeanUtils.instantiateClass(ctor, null, null, "foo", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPropertyDescriptors() throws Exception {
|
||||
PropertyDescriptor[] actual = Introspector.getBeanInfo(TestBean.class).getPropertyDescriptors();
|
||||
|
@ -458,4 +486,60 @@ public class BeanUtilsTests {
|
|||
}
|
||||
}
|
||||
|
||||
private static class BeanWithNullableTypes {
|
||||
|
||||
private Integer counter;
|
||||
|
||||
private Boolean flag;
|
||||
|
||||
private String value;
|
||||
|
||||
public BeanWithNullableTypes(@Nullable Integer counter, @Nullable Boolean flag, String value) {
|
||||
this.counter = counter;
|
||||
this.flag = flag;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getCounter() {
|
||||
return counter;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Boolean isFlag() {
|
||||
return flag;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private static class BeanWithPrimitiveTypes {
|
||||
|
||||
private int counter;
|
||||
|
||||
private boolean flag;
|
||||
|
||||
private String value;
|
||||
|
||||
public BeanWithPrimitiveTypes(int counter, boolean flag, String value) {
|
||||
this.counter = counter;
|
||||
this.flag = flag;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getCounter() {
|
||||
return counter;
|
||||
}
|
||||
|
||||
public boolean isFlag() {
|
||||
return flag;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -59,6 +59,14 @@ class BeanUtilsKotlinTests {
|
|||
assertEquals(12, bar.param2)
|
||||
}
|
||||
|
||||
@Test // gh-22531
|
||||
fun `Instantiate immutable class with nullable parameter`() {
|
||||
val constructor = BeanUtils.findPrimaryConstructor(Qux::class.java)!!
|
||||
val bar = BeanUtils.instantiateClass(constructor, "a", null)
|
||||
assertEquals("a", bar.param1)
|
||||
assertNull(bar.param2)
|
||||
}
|
||||
|
||||
@Test // SPR-15851
|
||||
fun `Instantiate mutable class with declared constructor and default values for all parameters`() {
|
||||
val baz = BeanUtils.instantiateClass(Baz::class.java.getDeclaredConstructor())
|
||||
|
@ -72,6 +80,8 @@ class BeanUtilsKotlinTests {
|
|||
|
||||
class Baz(var param1: String = "a", var param2: Int = 12)
|
||||
|
||||
class Qux(val param1: String, val param2: Int?)
|
||||
|
||||
class TwoConstructorsWithDefaultOne {
|
||||
|
||||
constructor()
|
||||
|
|
Loading…
Reference in New Issue