Wednesday, May 23, 2007

Spring with Hibernate and DWR

I recenty tried the combination spring 2.0.5, Hibernate 3.2.4 and DWR 2.0.1 with Java 5.
A lot of these frameworks have undergone some changes and so I thought it would be nice to put together the latest versions of them.

It is true that spring and DWR now have a much cleaner integration then with the previous version of DWR see Blog of Bram Smeets.

But i found that the examples of Bram were not using the DwrController that was described in his blog. I did not want to use the newer frameworks and then use the configuration style of the older versions. I wanted to use the newer configuration styles (with namespaces but not with tags).

I will add my configuration of the spring-servlet.xml to this blog and discuss each part of it. I kept all the configuration in the same file to keep things simple for this blog. But I certainly advise you to split up your configuration files for real applications.

Namespace Definitions

Spring now uses XML schema definitions, and for some people new to spring, this sometimes leads to confusion. The definition of my spring-servlet.xml file starts like this:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:dwr="http://www.directwebremoting.org/schema/spring-dwr"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.directwebremoting.org/schema/spring-dwr
http://www.directwebremoting.org/schema/spring-dwr-2.0.xsd">
These are all the namespaces that you will need to configure the 3 frameworks together.

Dao Configuration

The next thing to do is to configure the database connection, the hibernate session and the transaction manager. This is pretty well documented in the spring docs, so I'll just add it for the record:
<!--
***************************************************************************************
DAO configuration, transactions and database definitions
***************************************************************************************
-->

<!--
This special bean allows us to externalize the configuration of
the jdbc properties to an external file that contains all the
properties needed to create a DataSource connection.
The root of the location is in the WEB-INF directory.
-->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>WEB-INF/jdbc.properties</value>
</list>
</property>
</bean>

<!--
Definition of the JDBC DataSource implementation. This implementation
will perform connection pooling as well.
-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName">
<value>${jdbc.driverClassName}</value>
</property>
<property name="url">
<value>${jdbc.url}</value>
</property>
<property name="username">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
</bean>

<!--
Define the Hibernate ORM sessions.
-->
<bean id="dbSessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource"><ref local="dataSource"/></property>
<property name="mappingResources">
<list>
<value>address.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.use_outer_join">true</prop>
<prop key="hibernate.max_fetch_depth">4</prop>
<prop key="hibernate.cache.use_query_cache">false</prop>
<prop key="hibernate.bytecode.use_reflection_optimizer">false</prop>
</props>
</property>
</bean>

<!-- The DAO object to access the db through a hibernate session -->
<bean id="addressDao" class="mypackage.addressbook.dao.AddressDaoImpl">
<property name="sessionFactory"><ref local="dbSessionFactory"/></property>
</bean>

<!-- Hibernate Transaction Manager -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref bean="dbSessionFactory"/>
</property>
</bean>

<!-- Define the pointcuts for the transaction management -->
<aop:config>
<aop:pointcut id="addressServiceMethods" expression="execution(* mypackage.addressbook.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="addressServiceMethods"/>
</aop:config>

<!-- Define the transaction behaviour for the defined pointcuts -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
The datasource real parameters have been put in an external properties file that is put at the location WEB-INF/jdbc.properties).

The hibernate sessions are configured with the dbSessionFactory bean. The implementation of the addressDao bean is not important for this configuration, but it uses the hibernate templates that come with the spring framework.

A transaction manager has been defined and the transactions are defined with with the aop namespace (transactions will be started on any call on an object in the mypackage.addressbook.service package).

Publish the service bean for DWR

So far nothing special. Let's configure the service bean and publish it to DWR so we can call it from the client.
<!--
***************************************************************************************
Definition of the services
***************************************************************************************
-->

<bean id="addressService" class="mypackage.addressbook.service.AddressServiceImpl">
<aop:scoped-proxy/> <!-- This is needed or DWR gets confused. -->

<dwr:remote javascript="AddressService">
<dwr:exclude method="removePerson"/>
<dwr:exclude method="modifyPerson"/>
</dwr:remote>

<property name="addresDao"><ref bean="addressDao"/></property>
</bean>
The addressService bean has been defined just as you would define any other service bean, and then some dwr related stuff is added to it (in red).
The dwr tags just publish the service bean for use on the client side, and exclude the removePerson and the modifyPerson methods. This is a really clean configuration (i would even say it's fantastic), and and external dwr.xml file is no longer needed.

However I found that if a bean implements an interface, you need the aop part (orange part) , or DWR will not work with your bean. Your bean will be published, and you can even call a method from the DWR debug screen, but this will result in an error (java.lang.IllegalArgumentException: object is not an instance of declaring class).
I first thought that this was because I was missing a converter, but it turned out to be some scoping problem between DWR and spring (at least if I understud this blog correctly).
If your bean does not implement any interface, then you don't need this (I tested this with a wrapper object that did not implement any interface that delegated all calls to my service bean).
Any comments are welcome.

Configure the controllers

This section contains the definition of all web related stuff to configure the controllers and the url mappings.

<!--
***************************************************************************************
Definition of web related stuff
***************************************************************************************
-->

<!--
The Exeption handlers for the web application (add your own)
-->
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.Exception">generalError</prop>
</props>
</property>
</bean>

<!--
VIEWRESOLVER DEFINITIONS
-->

<!-- The standard viewresolver for the web application. Leaves priority 0 open so
you can still override this controller. -->

<!--
The definitions of this view resolver are in the WEB-INF/classes/views.properties file.
All views will inherit their settings from the modelView view definition in this file.
This makes defining the other views easier, since you don't need to specify the view class each time.
-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property name="basename"><value>views</value></property>
<property name="defaultParentView"><value>modelView</value></property>
</bean>


<!--
Url to controller mappings
-->

<!-- The mappings for our controllers. -->
<bean id="multiUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/index.do">addressController</prop>
</props>
</property>

<property name="order"><value>0</value></property>

<property name="interceptors">
<list>
<bean name="openSessionInViewInterceptor"
class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor">
<property name="sessionFactory"><ref bean="dbSessionFactory"/></property>
</bean>
</list>
</property>
</bean>


<bean id="dwrUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">

<props>
<prop key="/**/dwr/*">dwrController</prop>
<prop key="/**/dwr">dwrController</prop>
<prop key="/**/dwr/*.*">dwrController</prop>
<prop key="/**/dwr/**/*">dwrController</prop>
</props>
</property>

<property name="order"><value>1</value></property>
<property name="alwaysUseFullPath" value="true"/>


<property name="interceptors">
<list>
<bean name="openSessionInViewInterceptor"
class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor">
<property name="sessionFactory"><ref bean="dbSessionFactory"/></property>
</bean>
</list>
</property>
</bean>

<bean id="addressController" class="mypackage.addressbook.controllers.AddressController">
<property name="methodNameResolver">
<ref bean="addressMethodUrlMapping"/>
</property>

<property name="addressService">
<ref bean="addressService"/>
</property>
</bean>

<bean id="addressMethodUrlMapping" class="org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver">
<property name="mappings">
<props>
<prop key="/index.do">showPeople</prop>
</props>
</property>
</bean>
It should be clear that we have two handler mappings here, one for the application and one for DWR (in red). The requests to /dwr/* should all arrive on the dwrController. The dwrController is new and allows you to configure the dwr servlet without specifying it in the web.xml file.
There is also a priority order specified between the two url mappings. The one for DWR has the lowest priority.

The openSessionInViewInterceptor is needed because otherwise the hibernate session would be closed when the client browser launches a DWR request to the service layer. This is new, because I've used the previous version of DWR with spring 1.2.8 / hibernate 3.0.x and this was not needed with those versions. If someone can provide me more explanations about this, that would be cool.

To make it easier on the view layer for the application pages, I added this interceptor as well on that one (on the multiUrlMapping bean). But this has nothing to do with DWR.

DWR configuration

Finally there is the DWR configuration that is needed to add the dwrController and the converters for the DWR framework.
<!-- ========================= DWR DEFINITIONS ========================= -->
<dwr:controller id="dwrController" debug="true">
<dwr:config-param name="allowImpossibleTests" value="true"/>
</dwr:controller>


<dwr:configuration>
<dwr:convert type="bean" class="mypackage.addressbook.model.*" />
<dwr:convert type="bean" class="mypackage.addressbook.service.*" />
</dwr:configuration>


</beans>
The dwrController replaces the servlet that will takes all the client requests. The attribute debug allows you to open the debug page (i would not recommend this on a production system).
The allowImpossibleTests should allow you to run the methods from java.lang.Object, but i found that this did not work. Maybe i misunderstud the documentation about this.

That's all. Your application should now be ready to use spring/hibernate and DWR all together with the latest versions of the three frameworks.
Any comments are welcome.

4 comments:

Anonymous said...

Thank you very much!

Regards, Anders Båtstrand

Anonymous said...

Hi,

You have not mentioned anything about the servlet configuration: org.directwebremoting.servlet.DwrServlet

I tried to follow your instructions, but without servlet configuration, it was not complete.

Anonymous said...

The problem above was that i was strict in the web.xml, where i had to set the /dwr/* to be processed by the spring MVC servlet. Once that was configured, i didnot had the dwrServlet to be configured.

This is what i did in web.xml:
<servlet-mapping>
<servlet-name>SpringMVCServletName</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>

Hope this helps!!!.

Fran Díaz said...

Lots of thanks, your "orange code" was really helpful!!!