Внедрить bean-компонент в DataFetcher GraphQL

Я использую Spring и graphql-java (graphql-java-annotation) в своем проекте. Для получения части данных я использую DataFetcher для получения данных из службы (из базы данных).

Странно то, что myService всегда равно null. Кто-нибудь знает причину?

Сборщик данных

@Component
public class MyDataFetcher implements DataFetcher {

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

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

Схема

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

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

Моя служба

@Service
public class MyService {

    @Autowired
    private MyRepository myRepo;

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

Основной тест

@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();
}

person JasonS    schedule 03.01.2017    source источник
comment
Вы также можете опубликовать MyService.java?   -  person J J    schedule 03.01.2017
comment
я разместил MyService.java @jobin   -  person JasonS    schedule 03.01.2017
comment
есть ли ошибка при запуске приложения?   -  person J J    schedule 03.01.2017


Ответы (3)


Поскольку в graphql-java-annotation сборщик данных определяется аннотацией, он создается фреймворком (используя отражение для получения конструктора), поэтому он не может быть bean-компонентом.

Обходной путь, который я нашел для этого, устанавливает его как ApplicationContextAware, а затем я могу инициализировать некоторое статическое поле вместо bean-компонента. Не самая приятная вещь, но она работает:

@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);
    }
}

По сути, вы все равно получите новый экземпляр сборщика данных, инициализированный graphQL, но Spring также инициализирует его, и, поскольку myService является статическим, вы получите инициализированный.

person Nir Levy    schedule 03.01.2017
comment
Я не думаю, что это хорошее решение. MyDataFetcher является одноэлементным, а setApplicationContext вызывается один раз. Почему myService должен быть статичным? - person Matthias M; 04.02.2017

Хотя подход @Nir работает (и я часто использую его в прослушивателях событий JPA), объекты DataFetcher являются синглтонами, поэтому внедрение через статические свойства немного хакерское.

Однако метод GraphQL execute позволяет вам передавать объект в качестве контекста, который затем будет доступен в вашем объекте DataFetchingEnvironment внутри вашего DataFetcher (см. строку graphql.execute() ниже):

@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();
    }
}
person Mike    schedule 02.04.2017

Решение, предоставленное @Nir Levy, отлично работает. Просто чтобы сделать его более многоразовым здесь. Мы можем извлечь абстрактный класс, который инкапсулирует логику поиска bean-компонента и заставить работать автосвязывание для его подклассов.

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);
}

И подкласс может быть таким:

@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();
    }
}
person Yihan Zhao    schedule 24.01.2017