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();
}
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.
The solution provided by @Nir Levy works perfectly. Just to make it a bit more reusable here. We can extract an abstract class which encapsulate the bean lookup logic and make autowiring work for its subclasses.
public abstract class SpringContextAwareDataFetcher implements DataFetcher, ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public final Object get(DataFetchingEnvironment environment) {
return applicationContext.getBean(this.getClass()).fetch(environment);
}
protected abstract Object fetch(DataFetchingEnvironment environment);
}
And the subclass can be like this:
@Component
public class UserDataFetcher extends SpringContextAwareDataFetcher {
@Autowired
private UserService userService;
@Override
public String fetch(DataFetchingEnvironment environment) {
User user = (User) environment.getSource();
return userService.getUser(user.getId()).getName();
}
}
Though @Nir's approach works (and I often use it inside JPA Event Listeners), the DataFetcher objects are Singletons, so injecting via static properties is a little hacky.
However, GraphQL's execute
method allows you to pass in an object as a context, which will then be available in your DataFetchingEnvironment
object inside of your DataFetcher
(see the graphql.execute() line below):
@Component
public class GraphQLService {
@Autowired
MyService myService;
public Object getGraphQLResult() {
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}}", myService);
return result.getData();
}
}
public class MyDataFetcher implements DataFetcher {
@Override
public Object get(DataFetchingEnvironment environment) {
MyService myService = (MyService) environment.getContext();
return myService.getData();
}
}
来源:https://stackoverflow.com/questions/41440026/inject-bean-into-datafetcher-of-graphql