Wednesday, December 03, 2008

Using Wicket with Spring 2.5 annotations

We upgraded to Spring 2.5 and wanted to make use of the latest annotations in conjunction with JPA and Wicket's @SpringBean annotation. We are using Wicket 1.3.4 as of this writing. I felt it's worth blogging about, because even though it looks straightforward, there was pain and suffering required to get to what you see below.

Starting from the Wicket end, here's what we wanted to end up with. The middleware team created an API for the GUI to use.

MyBasePage.java


...
public class MyBasePage extends WebPage {
...
@SpringBean(name="myApiImpl")
private MyAPI myApi;
...


For a component like a Wicket page, this is all you need to do to get access to the spring bean. It will be automatically injected for you, provided everything is set up correctly (more on this below). Note the name attribute, which we found was necessary. It's the name of the API implementation class, with the first letter in lowercase.

For a non-component class like a data provider, you need to add a line to the constructor:

MyDataProvider.java


...
public class MyDataProvider extends SortableDataProvider {
...
@SpringBean(name="myApiImpl")
private MyAPI myApi;

public MyDataProvider()
{
...
// Injects the spring bean(s)
InjectorHolder.getInjector().inject(this);
}
...


Your Wicket application class needs to add a spring component instantiation listener:

MyApplication.java


...
public class MyApplication extends WebApplication {
...
private static final String[] appContexts = {"applicationContext.xml"};

@Override
protected void init()
{
...
final ApplicationContext ctx = new ClassPathXmlApplicationContext(
appContexts);
addComponentInstantiationListener(new SpringComponentInjector(this, ctx));
}
...


Your web.xml has to have a JPA OpenEntityManagerInViewFilter and a Spring ContextLoaderListener:

web.xml


<?xml version="1.0" encoding="UTF-8"?>
<web-app ...>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>

<filter>
<filter-name>openEntityManagerInViewFilter</filter-name>
<filter-class>
org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
</filter-class>
</filter>

<filter-mapping>
<filter-name>openEntityManagerInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
...
</web-app>


Now on to the middleware. Our API implementation has the @Service annotation, which designates it as a Spring service component. As far as I can tell, there's nothing too special about being a service component versus just an @Component, except that it makes it more readable. I think if you're using Spring Web Flow, it has more meaning. Note the injection of our DAO using the @Resource annotation:

MyApiImpl.java


...
@Service
public class MyApiImpl implements MyAPI, Serializable {
...
@Resource private MyDao dao;
...


The DAO implementation has the @Repository annotation, which designates it as a Spring DAO component, which does have some meaning in that it does exception translation from ugly JDBC exceptions to better Spring exceptions. Note we found we had to inject an extended PersistenceContext. This is not thread safe, but we haven't had any problems with it. We made the methods that persist data transactional using @Transactional. I've included one of them as an example. The generic objects with type T are persistent objects that are annotated with @Entity.

MyDaoImpl.java


...
@Repository
public class MyDaoImpl implements MyDao {
...
private EntityManager entityManager;
...
@PersistenceContext(type = PersistenceContextType.EXTENDED)
public void setEntityManager(final EntityManager entityManager)
{
this.entityManager = entityManager;
}
...
@Transactional
public void saveObject(final T t) {
try {
entityManager.persist(t);
entityManager.flush();
}
catch (Exception e) {
...
}
}
...


Finally, our Spring application context is quite small because most of what it does is enable all the annotations. The JPA persistence.xml file is quite standard and therefore not shown.

applicationContext.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" ...>

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="MYPERSISTENCEUNIT"/>
</bean>

<!-- For @Transactional -->
<tx:annotation-driven transaction-manager="txManager" />

<!-- For @PersistenceContext -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
<!-- Translates native resource exceptions to Spring's
DataAccessException hierarchy -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
<!-- For JSR-250 annotations such as @Resource -->
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>

<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
<property name="dataSource" ref="dataSource"/>
</bean>

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<!-- The usual properties -->
</bean>

<!-- Registers Spring's standard post-processors for annotation-based
configuration like @Repository -->
<context:annotation-config />
<!-- From this package on down is what Spring will scan for its
annotations like @Repository -->
<context:component-scan base-package="com.my"/>

</beans>

2 comments:

karakoyun said...

Great post. Thanks.

Mustafa Egilmezbilek

Ezhilraj Dhanasekaran said...

nice info