12

In a custom Wicket class, not unlike the following, I'm using a service bean which should be injected by Spring, as defined with the SpringBean annotation (from the wicket-spring project).

public class ReportExportFileModel extends AbstractReadOnlyModel<File> {

    @SpringBean(name = "reportService")
    ReportService reportService;

    ReportDto reportDto;

    ReportExportFileModel(ReportDto reportDto) {
        this.reportDto = reportDto;
    }

    @Override
    public File getObject() {
        try {
            return reportService.generatePentahoReport(reportDto);
        } catch (ReportGenerationException e) {
           // ...
        }
    }
}

However, this doesn't work: reportService.generatePentahoReport() fails with NullPointerException, because the bean has not been injected by Spring for some reason.

Curiously, I've used the exact same Model code as anonymous inner class on a Wicket Page, and there was no problem there.

How can I fix this?

Jonik
  • 80,077
  • 70
  • 264
  • 372

2 Answers2

17

The wicket dependency-injection works with classes implementing IComponentInstantiationListener. These application-level listeners are called whenever a Component is instantiated. This is the hook used for dependency injection of components.

The model classes do not have such a mechanism in place. Any model can directly implement IModel so there is no abstract base class which can call the listeners, unlike Component.

I use the following base class for my injected models (Wicket 1.5):

public abstract class InjectedDetachableModel<T> extends LoadableDetachableModel<T> {

    public InjectedDetachableModel() {
        Injector.get().inject(this);
    }

    public InjectedDetachableModel(T a_entity) {
        super(a_entity);
        Injector.get().inject(this);
    }
}

Edit: Summary of relevant differences between 1.4 and 1.5, taken from Wicket 1.5 migration guide:

Wicket 1.4

@Override
protected void init()
{
    // initialize Spring
    addComponentInstantiationListener(new SpringComponentInjector(this, applicationContext));
}

and

InjectorHolder.getInjector().inject(Object object)

Wicket 1.5:

@Override
protected void init()
{
    // initialize Spring
    getComponentInstantiationListeners().add(new SpringComponentInjector(this, applicationContext))
}

and

Injector.get().inject(Object object)
Jonik
  • 80,077
  • 70
  • 264
  • 372
bernie
  • 9,820
  • 5
  • 62
  • 92
  • 1
    This is Wicket 1.5 while Jonik still uses 1.4. – martin-g Nov 17 '11 at 06:26
  • Yeah, @martin-g is right; forgot to mention it in the question, but I'm on 1.4.17. If you edit your answer to cover both 1.4 and 1.5, I'll accept it :) – Jonik Nov 17 '11 at 08:41
7

Apparently Spring beans don't get automatically injected to other classes than Pages. I've run to this also with my custom WebRequestCycle class.

One easy solution is to trigger the injection manually using InjectorHolder.getInjector().inject(this).

So, writing the constructor like this makes the model work as intended:

ReportExportFileModel(ReportDto reportDto) {
    this.reportDto = reportDto;
    InjectorHolder.getInjector().inject(this);
}

Edit: ah, right after posting this, I found another SO question with a more accurate explanation of what's going on:

@SpringBean works only in any subclass of Component.

Community
  • 1
  • 1
Jonik
  • 80,077
  • 70
  • 264
  • 372
  • If anyone knows better solutions, or can explain why wicket-spring integration works like this, feel free to post other answers! – Jonik Nov 16 '11 at 16:49
  • 1
    bernie explained how it works. I can only add that in Wicket 1.5.3+ also Behavior is auto-injected like o.a.w.Component. – martin-g Nov 17 '11 at 06:27
  • Sorry for discussion like old-mafia-war ;) , Pls remember me WHY models cannot have been injected, has this decision deep philosophical background, or only implementation feature? AND HOW is orthodox wicket solution form models, resources etc – Jacek Cz Jan 11 '17 at 13:17
  • Self answer: because based on interface, not class. :) – Jacek Cz Jan 11 '17 at 14:45