...
Wicket does not manage the lifecycle of its components. This means that a page or a component can be created anywhere in the code by simply using the new operator. This makes it difficult to inject dependencies because it is difficult to intercept the creation of the component. A possible solution can be to use a singleton factory to create the components and subsequently inject them with dependencies. However, this approach is not very flexible because it is often more convinient convenient to have specific constructors in components rather then than the default empty constructor. ie
...
Code Block | ||||
---|---|---|---|---|
| ||||
... <servlet> <servlet-name>wicket</servlet-name> <servlet-class>org.apache.wicket.protocol.http.WicketServlet</servlet-class> <init-param> <param-name>applicationFactoryClassName</param-name> <param-value>org.apache.wicket.spring.SpringWebApplicationFactory</param-value> </init-param> <init-param> <param-name>applicationBean</param-name> <param-value>wicketApplication</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> ... <!-- The SpringWebApplicationFactory will need access to a Spring Application context, configured like this... --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> ... |
...
Warning |
---|
Please note that you cannot use constructor argument based injection with Wicket, only accessorsetter-based injection! |
It's possible to have your annotated dependencies automatically injected on construction. For this you have to install a SpringComponentInjector in your application.
...
Code Block |
---|
class MyApplication extends WebApplication { public void init() { super.init(); addComponentInstantiationListener(new SpringComponentInjector(this)); } } class EditContact extends WebPage { @SpringBean private ContactDao dao; @SpringBean(name="userDao") private UserDao userDao; public EditContact(long userId) { ... } } |
With Wicket >1.5 it is:
Code Block |
---|
class MyApplication extends WebApplication {
public void init() {
super.init();
getComponentInstantiationListeners().add(new SpringComponentInjector(this));
}
}
|
Here the page (or indeed anything derived from a Wicket Component
) will have its dependencies injected when created. [Constructor/superclass chaining down to the Component(final String id, final IModel model) constructor, where there's a call to Here the page (or indeed anything derived from a Wicket {{Component}}) will have its dependencies injected when created. _\[Constructor/superclass chaining down to the Component(final String id, final IModel model) constructor, where there's a call to getApplication().notifyComponentInstantiationListeners(this);\]_ Wiki Markup
When doing this it is important to remember not to initialize dependencies, to null or any other value, e.g.private ContactDao dao=null;
. Don't do this because the injector will run before the subclass initializes its fields, and so the dao=null
will override the created proxy with null.
Using annotation-based approach, you should not worry about serialization/deserialization of the injected dependencies as this is handled automatically, the dependencies are represented by serializable proxies. Also, you should not mark your dependency properties transient because if you do so, they won't be re-initialized upon deserialization.
wicket-spring-annot
project provides the SpringComponentInjector class for you. All you wicket-spring-annot
project provides the SpringComponentInjector class for you. All you have to do to get transparent injection working is to install the injector in your application like as shown above. SpringComponentInjector also supports automatic injection of wicket portlet apps.
Note |
---|
Unit Testing the Proxy Approach
Using the annotations approach requires that the servlet security manager you are using allows Reflection API calls, so you might have to change your security manager policy for the application. |
Unit Testing the Proxy Approach
Even when using automatic injection, unit testing is easy. Following is a sampel sample unit test that tests a fictional DeleteContactPage class.
...
Part 1 is the standard setup of the dependencies it is required to run the test. This particular test uses EasyMock library to make working with mock objects easier.
...
Part 3 is the setup of WicketTester and the SpringComponentInjector, which will inject our dao into classes which have the @SpringBean annotation
...
Code Block |
---|
public class DeleteContactPageTest extends TestCase { public void test() throws ServletException { // 1. setup dependencies and mock objects Contact contact=new Contact(); MockControl daoCtrl=MockControl.createControl(ContactDao.class); ContactDao dao=(ContactDao) daoCtrl.getMock(); daoCtrl.expectAndReturn(dao.load(10), contact); dao.delete(10); daoCtrl.replay(); // 2. setup mock injection environment AnnotApplicationContextMock appctx=new AnnotApplicationContextMock(); appctx.putBean("contactDao", dao); // 3. run the test WicketTester app=new WicketTester(); // You must add the following code to get it work with // For wicket 1.23.5 (I don't know with 1.3)use the code below //app.getApplication().addComponentInstantiationListener(new SpringComponentInjector(app.getApplication(), appctx)); // app.startPageFor wicket 1.2.5 use the code below. //app.addComponentInstantiationListener(new DeleteContactPage(new DummyHomePage()SpringComponentInjector(app, 10appctx)); app.startPage(new DeleteContactPage(new DummyHomePage(), 10)); app.assertRenderedPage(DeleteContactPage.class); app.assertComponent("confirmForm", Form.class); app.assertComponent("confirmForm:confirm", Button.class); app.setParameterForNextRequest("confirmForm:confirm", "pressed"); app.submitForm("confirmForm"); app.assertRenderedPage(DummyHomePage.class); daoCtrl.verify(); } } |
If you are using a AuthenticatedWebApplication along with a SpringInjector, you can configure the WicketTester this way:
}
|
If you are using a AuthenticatedWebApplication along with a SpringInjector, you can configure the WicketTester this way:
Code Block |
---|
...
AuthenticatedWebApplication authenticatedWebApp = new MyAuthenticatedWebApplication() {
@Override
public void init() {
addComponentInstantiationListener(new SpringComponentInjector(this, myApplicationContext));
}
};
WicketTester tester = new WicketTester(authenticatedWebApp);
...
|
As you can see, the use of the AnnotApplicationContextMock removes some noise from the test case.
Unit Testing Proxy Approach with Custom Session
When unit testing a Wicket application with a custom session and with Spring bean dependencies, it best to create an mock implementation of a WebApplication, overriding the newSession method, and passing this to the WicketTester. Here is an exampleL
Code Block |
---|
public void testDefaultUnauthenticatedPage() throws Exception {
CatalogManager catalogManager = createMock(CatalogManager.class);
ApplicationContextMock appctx=new ApplicationContextMock();
appctx.putBean("catalogManager", catalogManager);
WicketTester app=new WicketTester(createMockApplication());
SpringComponentInjector componentInjector = new SpringComponentInjector(app.getApplication(), appctx);
app.getApplication().addComponentInstantiationListener(componentInjector);
app.startPage(HighSecurePage.class);
app.assertRenderedPage(LoginPage.class);
}
private static final WebApplication createMockApplication() {
return new WebApplication() {
@Override
public Class getHomePage() {
return HomePage.class;
}
|
Code Block |
... @Override AuthenticatedWebApplication authenticatedWebApp =public newSession MyAuthenticatedWebApplication(newSession(Request request, Response response) { @Override return new RCNSession(request); } public void init() { @Override protected ISessionStore newSessionStore() { addComponentInstantiationListener(return new SpringComponentInjectorHttpSessionStore(this, myApplicationContext)); } }; WicketTester tester = new WicketTester(authenticatedWebApp); ... |
As you can see, the use of the AnnotApplicationContextMock removes some noise from the test case.
}
|
To expand on the WicketTester/Spring integration, many web applications are developed on J2EE compliant servers and use the server based datasources and connection pooling accessed through a JNDI call. The datasource/connection pool is then centralized in a spring Spring configuration file as a spring bean. The implication on testing is that this bean is invalid since there is no JNDI service; hence a local datasource/connection pool bean is substituted. Once the ant script or manual method is defined to substitute the datasource bean the application context can be integrated as follows:
...