Динамическая инъекция с использованием @SpringBean в калитке

У меня есть форма, которая на основе собранной информации создает отчет. У меня есть несколько источников для создания отчетов, но форма для них одинакова. Я попытался реализовать шаблон стратегии, используя интерфейс, реализующий службы генератора отчетов, но это привело к тому, что калитка жаловалась на проблемы с сериализацией различных частей генератора отчетов. Я хотел бы решить эту проблему, не дублируя код, содержащийся в форме, но мне не удалось найти информацию о динамической инъекции с помощью @SpringBean.

Вот грубый макет того, что у меня есть

public class ReportForm extends Panel {
    private IReportGenerator reportGenerator;
    
    public ReportForm(String id, IReportGenerator reportGenerator) {
        super(id);
        this.reportGenerator = reportGenerator;
        
        final Form<Void> form = new Form<Void>("form");
        this.add(form);

        ...

        form.add(new AjaxButton("button1") {
            private static final long serialVersionUID = 1L;
            @Override
            protected void onSubmit(AjaxRequestTarget target)
            {
                 byte[] report = reportGenerator.getReport(...);
                 ...
            }           
         });
    }
}

Если я сделаю это таким образом, калитка попытается сериализовать конкретный экземпляр reportGenerator. Если я аннотирую свойство reportGenerator с помощью @SpringBean, я получаю Concrete bean could not be received from the application context for class: IReportGenerator

Обновлено: я переработал реализации IRerportGenerator, чтобы иметь возможность аннотировать их с помощью @Component, и теперь, когда я использую аннотацию @SpringBean, я получаю More than one bean of type [IReportGenerator] found, you have to specify the name of the bean (@SpringBean(name = "foo")) or (@Named("foo") if using @javax.inject classes) in order to resolve this conflict. Именно этого я не хочу делать.

Голосование за закрытие, потому что вы задаете сразу несколько вопросов (почему я не могу внедрить сервисы Spring в Wicket и как мне спроектировать источники отчетов), а в вашем посте отсутствует Минимальный, полный и проверяемый пример. Тем не менее, я думаю, что вы идете в правильном направлении, используя шаблон стратегии, но я подозреваю, что в вашей конфигурации Spring не хватает нескольких настроек для хорошей работы с Wicket. По крайней мере, вы должны включить свою конфигурацию Spring и конфигурацию, которую вы используете для ее интеграции с Wicket.

Jeroen Steenbeeke 16.09.2022 15:38

Хорошо, я удаляю дополнительный вопрос и попытаюсь собрать какой-нибудь пример.

Peter 16.09.2022 16:54

Я подозреваю, что вам не хватает конфигурации, которая заставляет Spring создавать сериализуемые прокси-классы для ваших служб.

Jeroen Steenbeeke 16.09.2022 17:15

Аннотация SpringBean работает без проблем, если существует только одна реализация интерфейса. Однако в этом случае у меня есть несколько, и я не знаю, как указать, какой из них использовать динамически. Я знаю, что могу использовать имя с аннотацией SpringBean, но это сложно запрограммировать. Я ищу возможность указать, какую реализацию использовать во время создания экземпляра формы. Я смог передать конкретный экземпляр в форму, но, поскольку он не был проксирован, калитка попыталась сериализовать его, и именно здесь я столкнулся с проблемой.

Peter 16.09.2022 17:43

Ах я вижу. Итак, вам нужен механизм, похожий на квалификатор, который работает в сочетании с Wicket. Я думаю, что могу найти обходной путь, который может сработать для вас

Jeroen Steenbeeke 17.09.2022 18:10
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
5
57
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Насколько я могу найти, просматривая документацию и даже источник аннотации калитки @SpringBean, это невозможно. Самое близкое, что я получил, это явное создание прокси-сервера для компонента Spring на основе переданного класса. Как описано в 13.2.4 Использование прокси из главы проекта wicket-spring в Wicket в действии.

public class ReportForm extends Panel {
    private IReportGenerator reportGenerator;
    private Class<? extends IReportGenerator> classType;

    private static ISpringContextLocator CTX_LOCATOR = new ISpringContextLocator() {
        public ApplicationContext getSpringContext() {
            return ((MyApplication)MyApplication.get()).getApplicationContext();
        }
    };

    public ReportForm(String id, Class<? extends IReportGenerator> classType) {
        super(id);
        this.classType = classType;
        
        final Form<Void> form = new Form<Void>("form");
        this.add(form);

        ...

        form.add(new AjaxButton("button1") {
            private static final long serialVersionUID = 1L;
            @Override
            protected void onSubmit(AjaxRequestTarget target)
            {
                 byte[] report = getReportGenerator().getReport(...);
                 ...
            }           
         });
    }

    private <T> T createProxy(Class<T> classType) {
        return (T) LazyInitProxyFactory.createProxy(classType, new 
            SpringBeanLocator(classType, CTX_LOCATOR));
    }

    private IReportGenerator getReportGenerator() {
        if (reportGenerator = null) {
            reportGenerator = createProxy(classType);
        }
        return reportGenerator;
    }

}
Ответ принят как подходящий

Я думаю, что поведение, которого вы пытаетесь достичь, может быть реализовано с помощью небольшого обходного пути, путем введения bean-компонента Spring, который содержит все экземпляры IReportGenerator:

@Component
public class ReportGeneratorHolder {
  private final List<IReportGenerator> reportGenerators;

  @Autowired
  public ReportGeneratorHolder(List<IReportGenerator> reportGenerators) {
    this.reportGenerators = reportGenerators;
  }

  public Optional<IReportGenerator> getReportGenerator(Class<? extends IReportGenerator> reportGeneratorClass) {
    return reportGenerators.stream()
                           .filter(reportGeneratorClass::isAssignableFrom)
                           .findAny();

  }
}

Затем вы можете внедрить этот класс на свою страницу Wicket и передать нужный класс в качестве параметра-конструктора. В зависимости от вашей конфигурации Spring вам также может понадобиться ввести интерфейс для этого.

public class ReportForm extends Panel {
    @SpringBean
    private ReportGeneratorHolder reportGeneratorHolder;
    
    public ReportForm(String id, Class<? extends IReportGenerator> reportGeneratorClass) {
        super(id);

        IReportGenerator reportGenerator = reportGeneratorHolder
          .getReportGenerator(reportGeneratorClass)
          .orElseThrow(IllegalStateException::new);

        // Form logic omitted for brevity
    }
}

После выяснения того, что вы имели в виду под «необходимостью ввести интерфейс», это работает, но теперь я вернулся к проблеме сериализации. Wicket пытается сериализовать исполнителя и терпит неудачу.

Peter 17.09.2022 22:42

Под исполнителем вы подразумеваете экземпляр IReportGenerator, который вы извлекаете из держателя? Если он назначен переменной в (например) основном классе, и вы ссылаетесь на него из вложенного класса, то я полагаю, что он сериализуется вместе с вложенным классом. Решением в этом случае было бы не обращаться к переменной из внутреннего класса, а снова вызывать метод держателя.

Jeroen Steenbeeke 17.09.2022 23:34

Другие вопросы по теме