|
Spring has been edited by Erik van Oosten (Apr 18, 2008). Change summary: Note on using maven, removed remarks that apply to any wiki Table of contents
The IssuesMost problems with injecting dependencies from an IOC container come from the following
Wicket is an unmanaged frameworkWicket 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 to have specific constructors in components rather then the default empty constructor. ie class EditUserPage extends WebPage { public EditUserPage(long userId) {...} } ... setResponsePage(new EditUserPage(userId)); is far more convenient than class EditUserPage extends WebPage { public EditUserPage() {...} void setUserId(long userId) {...} } ... PageFactory factory=getPageFactory(); EditUserPage page=(EditUserPage)factory.createPage(EditUserPage.class); page.setUserId(userId); setResponsePage(page);
It's possible to have your annotated dependencies automatically injected on construction. For this you have to install a SpringComponentInjector in your application. Example: 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) { ... } } 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);] 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. 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 shown above. SpringComponentInjector also supports automatic injection of wicket portlet apps. Unit Testing the Proxy ApproachEven when using automatic injection unit testing is easy. Following is a sampel unit test that tests a fictional DeleteContactPage class. 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 ApplicationContextMock appctx=new ApplicationContextMock(); appctx.putBean("contactDao", dao); // 3. setup WicketTester and injector for @SpringBean WicketTester app=new WicketTester(); app.getApplication().addComponentInstantiationListener(new SpringComponentInjector(app.getApplication(), appctx )); // 4. run the test 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(); } } 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 2 is where we setup a mock spring environment. We setup a mock application context using the ApplicationContextMock object and add all beans necessary for the test. We also create the injector we will use to inject objects in this test. Part 3 is the setup of WicketTester and the SpringComponentInjector which will inject our dao into classes which have the @SpringBean annotation Part 4 is the test itself. We go through a setup of the page and a submission of its form. If you are using the annotations package to inject your pages, the testcase above can be further simplified by the use of AnnotApplicationContextMock that performs all the necessary wiring that is required to install the AnnotSpringInjector into the environment. Example 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 wicket 1.2.5 (I don't know with 1.3) //app.addComponentInstantiationListener(new SpringComponentInjector(app, appctx)); 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: ...
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. Beyond SpringThis technology can also be used to inject non-spring dependencies like JNDI or EJB3 beans. All it takes is a simple implementation of IFieldValueFactory and IProxyTargetLocator. Getting wicket-spring with Maven 2If you include wicket-spring as a dependency you will also get the full spring 2.0 jar. This is undesirable if you already have the smaller Spring jars on your dependency list. As far as I known, wicket-spring only needs spring-core.jar. Here is an example that includes spring-core.jar (2.5.3) and the wicket-spring jars (1.3.3): <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>2.5.3</version>
</dependency>
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-spring</artifactId>
<version>1.3.3</version>
<!-- exclude spring framework that wicket pulls in -->
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-spring-annot</artifactId>
<version>1.3.3</version>
<!-- exclude spring framework that wicket pulls in -->
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
</exclusions>
</dependency>
|
Unsubscribe or edit your notifications preferences
