Attempt properties binding after constructor

Prior to this commit, the `GraphQlArgumentBinder` would choose between
two strategies:
* if there is a constructor with arguments, instantiate the target and
  bind it using the constructor
* otherwise, use the default constructor and use bean properties binding

This commit extends the first case and attempts to further bind bean
properties after constructor instantiation.

Closes gh-1163
This commit is contained in:
Brian Clozel
2025-04-15 21:14:57 +02:00
parent 12871a0703
commit 0293299c08
2 changed files with 46 additions and 4 deletions

View File

@@ -292,7 +292,12 @@ public class GraphQlArgumentBinder {
}
try {
return BeanUtils.instantiateClass(constructor, constructorArguments);
Object target = BeanUtils.instantiateClass(constructor, constructorArguments);
// only attempt further properties binding if there were no errors
if (!bindingResult.hasErrors()) {
bindProperties(rawMap, ownerType, bindingResult, target);
}
return target;
}
catch (BeanInstantiationException ex) {
// Ignore, if we had binding errors to begin with
@@ -308,6 +313,11 @@ public class GraphQlArgumentBinder {
ArgumentsBindingResult bindingResult) {
Object target = BeanUtils.instantiateClass(constructor);
bindProperties(rawMap, ownerType, bindingResult, target);
return target;
}
private void bindProperties(Map<String, Object> rawMap, ResolvableType ownerType, ArgumentsBindingResult bindingResult, Object target) {
BeanWrapper beanWrapper = (this.fallBackOnDirectFieldAccess ?
new DirectFieldAccessFallbackBeanWrapper(target) : PropertyAccessorFactory.forBeanPropertyAccess(target));
@@ -343,10 +353,9 @@ public class GraphQlArgumentBinder {
bindingResult.rejectArgumentValue(key, value, "invalidPropertyValue", "Failed to set property value");
}
}
return target;
}
@SuppressWarnings("unchecked")
@Nullable
private <T> T convertValue(

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2023 the original author or authors.
* Copyright 2020-2025 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.
@@ -195,6 +195,16 @@ class GraphQlArgumentBinderTests {
assertThat(result).hasFieldOrPropertyWithValue("name", "test");
}
@Test
void mixedConstructorProperties() throws Exception {
Object result = bind("{\"name\":\"test\", \"age\":30}", ResolvableType.forClass(MixedConstructorPropertiesBean.class));
assertThat(result).isNotNull().isInstanceOf(MixedConstructorPropertiesBean.class);
assertThat(result).hasFieldOrPropertyWithValue("name", "test")
.hasFieldOrPropertyWithValue("age", 30);
}
@Test
void primaryConstructorWithBeanArgument() throws Exception {
@@ -457,6 +467,29 @@ class GraphQlArgumentBinderTests {
}
}
static class MixedConstructorPropertiesBean {
private final String name;
private int age;
public MixedConstructorPropertiesBean(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
static class PrimaryConstructorItemBean {