You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 5 Next »

Bookmarkable link

Table of contents
  • wicket-spring project is in the subversion alongside wicket
  • also look at wicket-spring-examples, wicket-spring-annot, and wicket-spring-annot-examples projects.
  • Some knowledge of spring is required
  • In wicket the Page class is a subclass of Component, so from here on the word component can represent either a component or a page.
  • This is only the first pass at the documentation so its far from perfect
  • Feel free to add new content and fix any mistakes you find.

The Issues

Most problems with injecting dependencies from an IOC container come from the following

  1. wicket is an unmanaged framework
  2. wicket components and models are often serialized

Wicket is an unmanaged framework

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

Wicket components and models are often serialized

Wicket keeps its tree of components in the session. In a clustered environment, session data needs to be replicated across the cluster. This is done by serializing objects in a cluster-node's session and deserializing them on another cluster-node's session. This presents a problem for dependency injection because it is not desirable to serialize the dependency. Dependencies often have references to other dependencies in the container, and so if one is serialized it will probably serialize a few others and can possibly cascade to serializing the entire container. To say the least, this is undesirable. Even if the cascading is not a problem and the dependency is serialized, when it deserializes it will no longer be part of the conainer - it will be a stand alone clone. This is also undesirable.

Solutions

Application Object Approach

Wicket applications have a global application object which is a subclass of Application. This global application object is only created once per application and is never serialized (since it contains no user-specific data and thus remains the same across all nodes in the cluster). These qualities make it a good candidate to act as a service locator for the rest of the application. Wicket allows you to provide a custom factory for creating this object, the wicket-contrib-spring project provides such a factory (SpringWebApplicationFactory) that, instead of creating an instance, pulls it out of the spring application context. Wicket keeps the instance of the application object in a threadlocal variable and provides various helper methods in components to get to it, so it is easy to retrieve dependencies in wicket components.

Example:

web.xml:

...
<servlet>
    <servlet-name>wicket</servlet-name>
    <servlet-class>wicket.protocol.http.WicketServlet</servlet-class>
    <init-param>
        <param-name>applicationFactoryClassName</param-name>
        <param-value>wicket.spring.SpringWebApplicationFactory</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>
...

applicationContext.xml:

...
<!-- setup wicket application -->
<bean id="wicketApplication" class="project.MyApplication">
    <property name="contactDao" ref="contactDao"/>
</bean>
...

code:

class MyApplication extends WebApplication {
   private ContactDao dao;
   public void setContactDao(ContactDao dao) { this.dao=dao; }
   public ContactDao getContactDao() { return dao; }
}

class BasePage extends WebPage {
    ContactDao getContactDao() {
        return ((MyApplication)getApplication()).getContactDao();
}

class EditContact extends BasePage {
    public EditContact(long id) {
        Form form=new Form("form",...) {
           public void onSubmit() {
               Contact contact=getContact();
               ...
               getContactDao().save(contact); // <================
               ...
            }
        }
    }
}

Pros:
*Simple
*Avoids serialization problem by never storing dependencies in components
Cons:
*Application class might get cluttered if the application has a lot of dependencies
*It is far too easy to accidentally keep a reference of the dependency and thus run into the serialization problem

Proxy-based Approach

It is also possible to create a dynamic proxy for the dependency that can be serialized and deserialized safely. The proxy would need to contain just enough information to lookup the dependency when needed, yet be small enough to not have a high impact on session size. This proxy will be known as a LazyInitProxy for the rest of the article. This solves the serialization problem, but not the injection problem. Later we will see how we can work around the injection problem. wicket-contrib-spring provides tools to create the proxies we need.

LazyInitProxy is very simple as far as proxies go. The gist of its invocation handler looks like this:

// this is only an example implementation
class LazyInitProxy implements InvocationHandler {
    // this is the cache for the dependency we are going to lookup on first access
    // notice this is declared as transient so that it will not be serialized with the proxy
    private transient target;

    public Object invocationHandler(Object proxy, Method method, Object[] args) {
        if (target==null) {
            target=lookupTarget();
        }
        return method.invoke(target, args);
    }
}

The only piece of information this proxy needs to have is the implementation of lookupTarget() method. For this the wicket-contrib-spring project provides the IProxyTargetLocator. This interface is very simple:

public interface IProxyTargetLocator extends Serializable
{
	Object locateProxyTarget();
}

LazyInitProxyFactory is the factory class that can generate a lazy init proxy given a proxy target locator and the proxy target's class. If the class is an interface the factory will generate a dynamic jdk proxy, otherwise a cglib proxy will be created.
Example:

applicationContext.xml
<!-- setup wicket application -->
<bean id="wicketApplication" class="project.MyApplication"/>

class MyApplication extends SpringWebApplication {
}

class EditContact extends WebPage {
   private ContactDao dao=LazyInitProxyFactory.createProxy(ContractDao.class,
      new IProxyTargetLocator() {
         public Object locateProxyTarget() {
            return ((MyApplication)Application.get()).getSpringContext().getBean("contactDao");
         }
      }
   }
}

Here our application class extends the SpringWebApplication class provided by wicket-contrib-spring. This subclass of WebApplication implements the spring ApplicationContextAware interface to provide our application with an easy way to get to spring context.

The problem with the above is its verbose nature. For every dependency you have to create the proxy and an object locator. To ease the pain wicket-contrib-spring provides the Injector class. This class can automatically inject dependencies into our application objects using an implementation of IFieldValueFactory interface. Injector.inject(Object object, IFieldValueFactory fieldValueLocator) will go through all of the fields of the object argument and assign to it the value it gets from the IFieldValueFactory.getFieldValue(Field field, Object fieldOwner) call. Using this class we can implement IFieldValueFactory that can create the proxies for us using some kind of a metadata associated with a field. The provided AnnotProxyFieldValueFactory class in the wicket-contrib-spring-jdk5 project does just that using SpringBean jdk5 annotation.
Example:

class EditContact extends WebPage {
   @SpringBean
   private ContactDao dao;

   @SpringBean(name="userDao")
   private UserDao userDao;

   protected EditContact(long userId) {
       ...
   }

   public static EditContact create(long userId) {
       EditContact c=new EditContact(userId);
       Injector.inject(c, new AnnotProxyFieldValueFactory(Application.get()));
   }

The above create factory method creates the page and injects it with dependencies. This has most of the advantages of ioc without actually being ioc.

Annotation-based Approach

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() {
        // notice in 2.0+ versions this is no longer necessary, 
        // simply drop the wicket-spring jar onto the classpath and start using @SpringBean
        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 it's 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-contrib-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 Approach

Even when using automatic injection unit testing is easy. Following is a sampe 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 obgject 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();
		
		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();
	}
}

As you can see, the use of the AnnotApplicationContextMock removes some noise from the test case.

Beyond Spring

This 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.

  • No labels