Skip to content

Commit 77bfb83

Browse files
authored
Merge pull request #853 from graphql-java-kickstart/bugfix/822
Warn at build when a resolver can't be called on its JDK dynamic proxy
2 parents 7d1edcc + 191029a commit 77bfb83

2 files changed

Lines changed: 28 additions & 1 deletion

File tree

‎src/main/kotlin/graphql/kickstart/tools/SchemaClassScanner.kt‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@ package graphql.kickstart.tools
33
import com.fasterxml.jackson.databind.JsonNode
44
import graphql.kickstart.tools.resolver.FieldResolver
55
import graphql.kickstart.tools.resolver.FieldResolverScanner
6+
import graphql.kickstart.tools.resolver.MethodFieldResolver
7+
import graphql.kickstart.tools.resolver.PropertyFieldResolver
68
import graphql.kickstart.tools.util.*
79
import graphql.language.*
810
import graphql.schema.GraphQLScalarType
911
import graphql.schema.idl.ScalarInfo
1012
import org.apache.commons.lang3.ClassUtils
1113
import org.apache.commons.lang3.reflect.TypeUtils
1214
import org.slf4j.LoggerFactory
15+
import java.lang.reflect.Field
1316
import java.lang.reflect.Member
1417
import java.lang.reflect.Method
18+
import java.lang.reflect.Modifier
1519
import java.lang.reflect.ParameterizedType
1620

1721
/**
@@ -524,6 +528,7 @@ internal class SchemaClassScanner(
524528
private fun scanResolverInfoForPotentialMatches(type: ObjectTypeDefinition, resolverInfo: ResolverInfo) {
525529
type.withExtensions(typeExtensions).flatMap { it.fieldDefinitions }.forEach { field ->
526530
val fieldResolver = fieldResolverScanner.findFieldResolver(field, resolverInfo)
531+
warnIfNotCallableOnResolver(type, fieldResolver)
527532

528533
fieldResolversByType.getOrPut(type) { mutableMapOf() }[fieldResolver.field] = fieldResolver
529534

@@ -533,6 +538,28 @@ internal class SchemaClassScanner(
533538
}
534539
}
535540

541+
// a JDK dynamic proxy only implements its target's interfaces, so the target's own methods and fields can't be called on it
542+
// TODO throw a FieldResolverError instead in 15.0.0 (#868)
543+
private fun warnIfNotCallableOnResolver(type: ObjectTypeDefinition, fieldResolver: FieldResolver) {
544+
val source = fieldResolver.search.source ?: return
545+
val member: Member = when (fieldResolver) {
546+
is MethodFieldResolver -> fieldResolver.method
547+
is PropertyFieldResolver -> fieldResolver.property
548+
else -> return
549+
}
550+
if (Modifier.isStatic(member.modifiers) || member.declaringClass.isInstance(source)) {
551+
return
552+
}
553+
554+
// class-based proxies don't delegate field reads to the target, so a field needs a getter too
555+
val fix = if (member is Field) "Add a getter for the field and use class-based proxies" else "Use class-based proxies instead"
556+
log.warn(
557+
"Field '${type.name}.${fieldResolver.field.name}' resolves to ${member.declaringClass.name}.${member.name}, but the resolver is a " +
558+
"${source.javaClass.name}, which isn't a ${member.declaringClass.name}, so every query of the field will fail. This happens " +
559+
"with JDK dynamic proxies, which only implement interfaces. $fix, e.g. proxyTargetClass = true in Spring."
560+
)
561+
}
562+
536563
private fun handleFoundType(match: TypeClassMatcher.Match) = when (match) {
537564
is TypeClassMatcher.ScalarMatch -> handleFoundScalarType(match.type)
538565
is TypeClassMatcher.ValidMatch -> handleFoundType(match.type, match.javaType, match.reference)

‎src/main/kotlin/graphql/kickstart/tools/resolver/PropertyFieldResolver.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ internal class PropertyFieldResolver(
1818
field: FieldDefinition,
1919
search: FieldResolverScanner.Search,
2020
options: SchemaParserOptions,
21-
private val property: Field
21+
val property: Field
2222
) : FieldResolver(field, search, options) {
2323

2424
override fun createDataFetcher(): DataFetcher<*> {

0 commit comments

Comments
 (0)