For the past several months, I have been looking for ways to integrate GWT and Spring in a way that is not as painful as it appeared to be at first.
Certainly, the new /war deployment structure of GWT 1.6 is a massive stride forward, especially for those who, like me, think that Tomcat is still the easiest way to test and deploy a web application.
Obviously, by using GWT, one no longer needs Spring MVC (it could be integrated, but, honestly the pain and suffering seem hardly worth the result) but dependency injection, at least server-side, and all the other goodies that come with Spring are a massive win-win.
In particular, I am a strong believer in the value of JPA – not least, because allows one to use ORM outside of any particular J2EE container (as it happens, I’ve an application currently running on a server machine, as a stand-alone Java app, that uses JPA to persist data on a MySQL database).
Unfortunately, it turns out that it’s not so simple to make GWT and Spring’s flavour of JPA co-operate nicely, at least not if you are developing using Eclipse, and probably not even outside of Eclipse development environment.
To understand why, one needs to know that, to work outside of a J2EE container, Spring’s JPA implementation needs to ‘decorate’ the entity classes with Aspects that will then require one to use a specific Java Agent (see also the javadoc for java.lang.instrument):
12.6.1.3.2. General LoadTimeWeaver
For environments where class instrumentation is required but are not supported by the existing LoadTimeWeaver implementations, a JDK agent can be the only solution. For such cases, Spring provides InstrumentationLoadTimeWeaver which requires a Spring-specific (but very general) VM agent (spring-agent.jar):
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver">
</property>
</bean>Note that the virtual machine has to be started with the Spring agent, by supplying the following JVM options:
-javaagent:/path/to/spring-agent.jar
[from Spring Reference manual, ver. 2.06]
Now, this work absolutely fine if you are using a Spring-powered Java application, and you want (or need) to use an EntityManagerFactory that is configured as follows:
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
<property name="showSql" value="false" />
<property name="generateDdl" value="false" />
<property name="databasePlatform" value="oracle.toplink.essentials.platform.database.MySQL4Platform" />
</bean>
</property>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
</property>
</bean>
this has the advantage that one can configure multiple data sources (eg, one for testing and one for production) pointing at different schemas, or even different instances of MySQL running on different servers:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}" />
<property name="driverClassName" value="${jdbc.driver}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<bean id="testDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.test.url}" />
<property name="driverClassName" value="${jdbc.driver}" />
<property name="username" value="${jdbc.test.username}" />
<property name="password" value="${jdbc.test.password}" />
</bean>
(the ${jdbc.*} expressions are parsed by a PropertyPlaceholderConfigurer and substituted from a properties files specified in its location property)
This will work by running your application with a command such as:
java -javaagent:/var/lib/spring-framework-2.0.6/dist/weavers/spring-agent.jar com.ibw.application.MyClass
Now, to make Spring work in my GWT-based web app, I have (tentatively, I’m looking for a smarter / more general approach — suggestions warmly welcome!) resorted to the oldest trick in the book: a Singleton, called at initialization by my servlets (either GWT RPC RemoteService implementations, or POS – Plain Old Servlets):
public class QuoteServiceImpl extends RemoteServiceServlet implements QuoteService {
private static final int MIN_SYM_LEN = 3;
private StockServiceConnector connector;
private StocksApplicationContext context = StocksApplicationContext.getInstance();
// rest of servlet code follows here....
}
where the StocksApplicationContext Singleton takes care of all the plumbing necessary to get Spring’s DI framework started:
// Copyright Infinite Bandwidth ltd (c) 2009. All rights reserved.
// Created 28 Apr 2009, by marco
package com.ibw.stocks.server;
import java.util.logging.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Singleton class that acts as the main application context for the server-side
* classes: it will initialise the Spring ApplicationContext, and generate the beans.
*
* This will also act as factory class (typically, just a thin wrapper around the Spring
* bean factories) for the main application server component(s): the business layer, DAOs, etc.
*
* To use, just invoke the {@link #getInstance() static getInstance} method, and
* invoke the desired methods on the returned instance. A further convenience method (
* {@link #getBean(String) getBean}) is provided, as a thin wrapper around Spring's
* {@link ApplicationContext#getBean(String) ApplicationContext's getBean} method.
*
* <h4>All rights reserved Infinite Bandwidth ltd (c) 2009</h4>
* @author Marco Massenzio
* @version 1.0
*/
public class StocksApplicationContext {
private static final String BEAN_DEFS_XML = "com/ibw/stocks/beans.xml";
private static Logger log = Logger.getLogger("com.ibw.stocks");
private ApplicationContext ctx;
private static StocksApplicationContext instance;
private StocksApplicationContext() {
log.info("Creating a StocksApplicationContext singleton. Bean definitions from " + BEAN_DEFS_XML);
ctx = new ClassPathXmlApplicationContext(BEAN_DEFS_XML);
}
public static StocksApplicationContext getInstance() {
if (instance == null) {
instance = new StocksApplicationContext();
log.info("A StocksApplicationContext factory has been created");
}
return instance;
}
public Object getBean(String beanName) {
return ctx.getBean(beanName);
}
}
Back to JPA, I thought that, having done all the above, I was pretty much done: unfortunately, when you launch your GWT application (either in Hosted Mode, or from within a browser) you get an exception, and it’s clear that the javaagent option has not been picked up by either Jetty (currently, the embedded server in GWT’s hosted mode).
The only solution I have found so far, is to use a much simpler EntityManagerFactory (*) setup (in /com/ibw/stocks/beans.xml):
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="StocksPU">
</bean>
that refers back to a Persistence Unit (in META-INFO/persistence.xml):
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
version="1.0" >
<persistence-unit name="StocksPU" type="RESOURCE_LOCAL">
<class>com.ibw.stocks.model.Currency</class>
<class>com.ibw.stocks.model.Index</class>
<class>com.ibw.stocks.model.Stock</class>
<class>com.ibw.stocks.model.Quote</class>
<properties>
<property name="toplink.jdbc.driver"
value="com.mysql.jdbc.Driver" />
<property name="toplink.jdbc.url"
value="jdbc:mysql://my.server.com:3306/myschema" />
<property name="toplink.jdbc.user" value="auser">
<property name="toplink.jdbc.password" value="asifimtellingyou">
</properties>
</persistence-unit>
</persistence>
This works, but leaves me unhappy, as I now need to configure my data persistence configuration in an XML file (persistence.xml) instead of using Spring’s beans properties (and the ability to inject values programmatically): I plan to explore next the possibility of using multiple Persistence Units, and use those to configure multiple configurations (eg, development, stating and production).
(*)UPDATE: although in the original post, this may be overlooked: this is, in fact, what tripped Yaakov (see comments below) and caused the server to complain about “No persistence unit with name ‘xxxx’ found.”
It took us an entertaining googlechat session when I felt compelled to explain to him things such as where to put WEB-INF, and then he eventually figured out what the problem was by himself, tracking down an old post of mine to GWT Group.
Why was that “entertaining”? well, this is the very same Yaakov Chaikin of Core Servlets and JavaServer Pages … I just had no idea at the time!
Leave a Reply