Bookmarkable link
|
- 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
- wicket is an unmanaged framework
- 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 convenient to have specific constructors in components rather than 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>
<init-param>
<param-name>applicationBean</param-name>
<param-value>wicketApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
...
<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:
...
<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:
class LazyInitProxy implements InvocationHandler {
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 setter-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) {
...
}
}
With Wicket >1.5 it is:
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 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.
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 have to do to get transparent injection working is to install the injector in your application as shown above. SpringComponentInjector also supports automatic injection of wicket portlet apps.
| 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 sample unit test that tests a fictional DeleteContactPage class.
public class DeleteContactPageTest extends TestCase {
public void test() throws ServletException {
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();
ApplicationContextMock appctx=new ApplicationContextMock();
appctx.putBean("contactDao", dao);
WicketTester app=new WicketTester();
app.getApplication().addComponentInstantiationListener(new SpringComponentInjector(app.getApplication(), 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();
}
}
Part 1 is the standard setup of the dependencies 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 {
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();
AnnotApplicationContextMock appctx=new AnnotApplicationContextMock();
appctx.putBean("contactDao", dao);
WicketTester app=new WicketTester();
app.getApplication().addComponentInstantiationListener(new SpringComponentInjector(app.getApplication(), 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.
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
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;
}
@Override
public Session newSession(Request request, Response response) {
return new RCNSession(request);
}
@Override
protected ISessionStore newSessionStore() {
return new HttpSessionStore(this);
}
};
}
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 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:
public class TestMyWicketApplication extends TestCase {
public void testBasicRender() {
MyWicketApplication webApp = new MyWicketApplication(){
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] {"spring-config.xml"});
@Override
public void init() {
addComponentInstantiationListener(new SpringComponentInjector(this, context));
}
};
WicketTester tester=new WicketTester(webApp);
Map paramMap = new TreeMap();
paramMap.put("displayname", "Test User");
paramMap.put("loginname", "testuser");
PageParameters parameters = new PageParameters(paramMap);
tester.startPage(WelcomePage.class, parameters);
tester.assertRenderedPage(WelcomePage.class);
}
}
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.
Using @SpringBean beyond Wicket
See SpringBean outside Wicket.
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>