Skip to content

Commit 0293299

Browse files
committed
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
1 parent 12871a0 commit 0293299

File tree

2 files changed

+46
-4
lines changed

2 files changed

+46
-4
lines changed

spring-graphql/src/main/java/org/springframework/graphql/data/GraphQlArgumentBinder.java

+12-3
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,12 @@ private Object bindMapToObjectViaConstructor(
292292
}
293293

294294
try {
295-
return BeanUtils.instantiateClass(constructor, constructorArguments);
295+
Object target = BeanUtils.instantiateClass(constructor, constructorArguments);
296+
// only attempt further properties binding if there were no errors
297+
if (!bindingResult.hasErrors()) {
298+
bindProperties(rawMap, ownerType, bindingResult, target);
299+
}
300+
return target;
296301
}
297302
catch (BeanInstantiationException ex) {
298303
// Ignore, if we had binding errors to begin with
@@ -308,6 +313,11 @@ private Object bindMapToObjectViaSetters(
308313
ArgumentsBindingResult bindingResult) {
309314

310315
Object target = BeanUtils.instantiateClass(constructor);
316+
bindProperties(rawMap, ownerType, bindingResult, target);
317+
return target;
318+
}
319+
320+
private void bindProperties(Map<String, Object> rawMap, ResolvableType ownerType, ArgumentsBindingResult bindingResult, Object target) {
311321
BeanWrapper beanWrapper = (this.fallBackOnDirectFieldAccess ?
312322
new DirectFieldAccessFallbackBeanWrapper(target) : PropertyAccessorFactory.forBeanPropertyAccess(target));
313323

@@ -343,10 +353,9 @@ private Object bindMapToObjectViaSetters(
343353
bindingResult.rejectArgumentValue(key, value, "invalidPropertyValue", "Failed to set property value");
344354
}
345355
}
346-
347-
return target;
348356
}
349357

358+
350359
@SuppressWarnings("unchecked")
351360
@Nullable
352361
private <T> T convertValue(

spring-graphql/src/test/java/org/springframework/graphql/data/GraphQlArgumentBinderTests.java

+34-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2023 the original author or authors.
2+
* Copyright 2020-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -195,6 +195,16 @@ void primaryConstructor() throws Exception {
195195
assertThat(result).hasFieldOrPropertyWithValue("name", "test");
196196
}
197197

198+
@Test
199+
void mixedConstructorProperties() throws Exception {
200+
201+
Object result = bind("{\"name\":\"test\", \"age\":30}", ResolvableType.forClass(MixedConstructorPropertiesBean.class));
202+
203+
assertThat(result).isNotNull().isInstanceOf(MixedConstructorPropertiesBean.class);
204+
assertThat(result).hasFieldOrPropertyWithValue("name", "test")
205+
.hasFieldOrPropertyWithValue("age", 30);
206+
}
207+
198208
@Test
199209
void primaryConstructorWithBeanArgument() throws Exception {
200210

@@ -457,6 +467,29 @@ public String getName() {
457467
}
458468
}
459469

470+
static class MixedConstructorPropertiesBean {
471+
472+
private final String name;
473+
474+
private int age;
475+
476+
public MixedConstructorPropertiesBean(String name) {
477+
this.name = name;
478+
}
479+
480+
public String getName() {
481+
return this.name;
482+
}
483+
484+
public int getAge() {
485+
return this.age;
486+
}
487+
488+
public void setAge(int age) {
489+
this.age = age;
490+
}
491+
}
492+
460493

461494
static class PrimaryConstructorItemBean {
462495

0 commit comments

Comments
 (0)