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

Compare with Current View Page History

« Previous Version 21 Next »

Bookmarkable link

Table of contents
  • Wicket-spring project is in the subversion alongside wicket.
  • Please read on for some Maven tips.
  • Also look at wicket-examples org.apache.wicket.spring packages for examples.
  • 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.

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>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>
    <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. The @SpringBean annotation can be found in wicket-spring-annot.
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);
       InjectorHolder.getInjector().inject(c);
   }

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

Please note that you cannot use constructor argument based injection with Wicket, only accessor-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.

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 Approach

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

Getting wicket-spring with Maven 2

If 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>
  • No labels