Search code examples
javaspringgraphqlgraphql-java

Inject bean into DataFetcher of GraphQL


I'm using Spring & graphql-java (graphql-java-annotation) in my project. For retrieving data part, i'm using a DataFetcher to get data from a service (from database).

The weird thing is that: myService is always null. Anyone know the reason?

DataFetcher

@Component
public class MyDataFetcher implements DataFetcher {

    // get data from database
    @Autowired
    private MyService myService;

    @Override
    public Object get(DataFetchingEnvironment environment) {
        return myService.getData();
    }
}

Schema

@Component
@GraphQLName("Query")
public class MyGraphSchema {

    @GraphQLField
    @GraphQLDataFetcher(MyDataFetcher.class)
    public Data getData() {
        return null;
    }
}

MyService

@Service
public class MyService {

    @Autowired
    private MyRepository myRepo;

    @Transactional(readOnly = true)
    public Data getData() {
        return myRepo.getData();
    }
}

Main test

@Bean
public String testGraphql(){
    GraphQLObjectType object = GraphQLAnnotations.object(MyGraphSchema.class);
    GraphQLSchema schema = newSchema().query(object).build();
    GraphQL graphql = new GraphQL(schema);

    ExecutionResult result = graphql.execute("{getData {id name desc}}");;
    Map<String, Object> v = (Map<String, Object>) result.getData();
    System.out.println(v);
    return v.toString();
}

Solution

  • Since in graphql-java-annotation the data fetcher is defined by annotation, it is constructed by the framework (using reflection to get the constructor), thus it can't be a bean.

    The workaround I've found for this is setting it as ApplicationContextAware, and then I can initialize some static field instead of a bean. Not the nicest thing, but it works:

    @Component
    public class MyDataFetcher implements DataFetcher, ApplicationContextAware {
    
        private static MyService myService;
        private static ApplicationContext context;
    
        @Override
        public Object get(DataFetchingEnvironment environment) {
            return myService.getData();
        }
    
        @override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansExcepion {
            context = applicationContext;
            myService = context.getBean(MyService.class);
        }
    }
    

    Basically you'll still get a new instance of the data fetcher initialized by graphQL, but also spring will initialize it, and since the myService is static, you'll get the initialized one.