Peter
Peter

Reputation: 473

Dynamic injection using @SpringBean in wicket

I have a form that based on collected information generates a report. I have multiple sources from which to generate reports, but the form for them is the same. I tried to implement strategy pattern using an interface implementing report generator services, but that led to wicket complaining about serialization issues of various parts of the report generator. I would like to solve this without duplicating the code contained in the form, but I have not been able to find information on dynamic injection with @SpringBean.

Here is a rough mock up of what I have

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

If I do it this way, wicket tries to serialize the concrete instance of reportGenerator. If I annotate the reportGenerator property with @SpringBean I receive Concrete bean could not be received from the application context for class: IReportGenerator

Edit: I have reworked implementations of IRerportGenerator to be able to annotate them with @Component and now I when I use @SpringBean annotation I get 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. Which is exactly what I don't want to do.

Upvotes: 0

Views: 221

Answers (2)

Peter
Peter

Reputation: 473

As far as I am able to find, looking through documentation and even the source for wicket @SpringBean annotation, this isn't possible. The closest I got is with explicitly creating a proxy for a Spring bean based on class passed. As described in 13.2.4 Using proxies from the wicket-spring project chapter in Wicket in Action.

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

}

Upvotes: 0

Jeroen Steenbeeke
Jeroen Steenbeeke

Reputation: 4013

I think the behavior you're trying to achieve can be done with a slight workaround, by introducing a Spring bean that holds all IReportGenerator instances:

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

  }
}

You can then inject this class into your Wicket page, and pass the desired class as a constructor-parameter. Depending on your Spring configuration you might need to introduce an interface for this as well.

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

Upvotes: 1

Related Questions