@@ -3,15 +3,19 @@ package graphql.kickstart.tools
33import com.fasterxml.jackson.databind.JsonNode
44import graphql.kickstart.tools.resolver.FieldResolver
55import graphql.kickstart.tools.resolver.FieldResolverScanner
6+ import graphql.kickstart.tools.resolver.MethodFieldResolver
7+ import graphql.kickstart.tools.resolver.PropertyFieldResolver
68import graphql.kickstart.tools.util.*
79import graphql.language.*
810import graphql.schema.GraphQLScalarType
911import graphql.schema.idl.ScalarInfo
1012import org.apache.commons.lang3.ClassUtils
1113import org.apache.commons.lang3.reflect.TypeUtils
1214import org.slf4j.LoggerFactory
15+ import java.lang.reflect.Field
1316import java.lang.reflect.Member
1417import java.lang.reflect.Method
18+ import java.lang.reflect.Modifier
1519import 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)
0 commit comments