Using Groovy, Spring and JavaConfig
Last week Romain Guy blogged about using Groovy and JPA together. This was followed by a blog entry from Guillaume Laforge where he combined Groovy and Guice. Both of these examples require the new support for annotations that can be found in recent SNAPSHOT builds of Groovy 1.1. So how about trying this out for an application configured using Spring? Well, that's what I have been playing around with recently and it's worked very well so far.
I must say that I was pleasantly surprised by how well this worked, considering that the annotation support in Groovy is very recent. Not sure how well this approach would work in a web application since we would have to find a way of either compiling the Groovy classes or loading them dynamically maybe using Spring's dynamic language support. I'll get back to this in the near future. For now, what I have been experimenting with works in a standalone application started using the "groovy" script provided in the Groovy download. See these instructions for how to install Groovy 1.0 and Romain's blog entry for instructions how to upgrade to the Groovy 1.1 SNAPSHOT.
The application I built is deliberately very simple, but it's a multi-layered application so we get to use the common techniques used for more complex applications like dependency injection, transaction support, data access support etc. It all starts with a class called MyApp.groovy. It has a reference to a component named MyService with a method called AddAPerson. This method takes a Person object as the only parameter. This is the entire application for now, but to get all this working we are going to need quite a few pieces.
MyApp.groovy
class MyApp {
def MyService service
void run() {
assert service != null
Person p = new Person()
p.name = "Thomas"
service.addAPerson(p)
}
}
So what does the service look like? Well, it's again very simple - just one method that calls into the data repository layer to persist the Person object. In the implementation class I'm using the @Transactional annotation from Spring to provide transaction support for the service methods. We will see later how this is applied in the Spring configuration. Since we are using Groovy, there is no need to provide setters/getters for the properties. This makes the code much cleaner and we can still inject the dependencies using regular Spring bean configuration.
MyService.groovy
public interface MyService {
void addAPerson(Person p)
}
MyServiceImpl.groovy
import org.springframework.transaction.annotation.Transactional
@Transactional
class MyServiceImpl implements MyService {
def PersonRepository personRepository
void addAPerson(Person p) {
personRepository.add(p)
}
}
Moving on to the data access layer I have defined a repository interface for the add(Person p) method.
PersonRepository.groovy
interface PersonRepository {
void add(Person p)
}
To implement the data access I decided to again use JPA since it worked well for Romain. Here is the repository implementation followed by the Person class with the JPA annotations. There are two annotations in the repository implementation. The first is @Repository which is a Spring annotation providing exception translation for data access code. In this case any JPA exceptions will be translated to a corresponding exception from the Spring DataAccessException hierarchy. This is beneficial since you can rely on only dealing with one type of exceptions even if you have a mix of data access technologies in your data access layer. The second annotation is @PersistenceContext and that is a JPA annotation used to inject an EntityManager.
PersonRepositoryImpl.groovy
import javax.persistence.*
import org.springframework.stereotype.Repository;
@Repository
class PersonRepositoryImpl implements PersonRepository {
@PersistenceContext
EntityManager entityManager
void add(Person p) {
entityManager.persist(p)
}
}
Person.groovy
import javax.persistence.*
@Entity
class Person {
@Id @GeneratedValue
Long id
String name
}
This is all the code we need for this application for now. Next we will look at what is needed to wire this application up and execute it. Keep in mind that the amount of code needed for configuration seems larger than the application code for now, but if we built more functionality for the application itself we would still have about the same amount of configuration code.
First we need a class that will bootstrap the whole application - let's call this class MyAppStart.groovy. It's a simple script that prints the Groovy version we are using, loads up a Spring application context containing the data access configuration. This context is used as the parent context for the application context that wires up the application and the service layer. Then the "myApp" bean is retrieved and we run the application. To keep things interesting I decided to use the new, still experimental, Spring JavaConfig for this second context. Spring JavaConfig let's you configure Spring using Java and annotations rather than XML. Craig Walls did blog about using Spring JavaConfig and Guice recently.
MyAppStart.groovy
import org.springframework.context.*
import org.springframework.context.support.*
import org.springframework.context.java.*
println "Groovy " + org.codehaus.groovy.runtime.InvokerHelper.getVersion()
ApplicationContext pac = new ClassPathXmlApplicationContext("spring-data-access.xml")
AnnotationApplicationContext aac = new AnnotationApplicationContext(pac)
aac.setConfigClasses((Class[])[MyConfig.class])
aac.refresh()
app = aac.getBean("myApp")
app.run()
The "spring-data-access.xml" context file is a standard JPA configuration with the following entries
- personRepository - a singleton bean for the PersonRepository implementation
- PersistenceExceptionTranslationPostProcessor - the bean responsible for handling the @Repository annotation
- PersistenceAnnotationBeanPostProcessor - the bean handling the dependency injection for the @PersistenceContext annotation
- entityManagerFactory - the factory used to create the EntityManager used by the previous bean. Here we specify Hibernate (using Hibernate 3.2 with JPA support from Hibernate-Annotations and Hibernate-EntityManager projects) as the persistence provider and supply any properties needed for DDL generation and the database dialect. (The H2 database is my current favorite - comes with a very nice web based management console app)
- transactionManager - the TransactionManager used for the JPA implementation
- dataSource - a DataSource and connection pool
- propertyConfigurer - the bean responsible for replacing the jdbc property placeholders with values from jdbc.properties file
spring-data-access.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<bean name="personRepository" class="PersonRepositoryImpl" autowire="byName"/>
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="persistenceUnitName" value="test"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="false"/>
<property name="generateDdl" value="true"/>
<property name="databasePlatform" value="org.hibernate.dialect.H2Dialect"/>
</bean>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driverClassName}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="minPoolSize" value="2"/>
<property name="maxPoolSize" value="15"/>
<property name="maxStatements" value="50"/>
</bean>
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="jdbc.properties"/>
</bean>
</beans>
To complete the JPA configuration we also need a META-INF/persistence.xml file. The one I'm using is minimal, just containing the list of persistent classes and the persistence unit name and transaction type.
META-INF/persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="test" transaction-type="RESOURCE_LOCAL">
<class>Person</class>
</persistence-unit>
</persistence>
Now to the more interesting configuration for the application and the service layer. Like I mentioned earlier I am using the Spring JavaConfig support here. The class is annotated with @Configuration to indicate that this is indeed a spring application context configuration. Three beans are defined using the @Bean annotation. The first one is the "myApp" bean that is the application itself and it gets the service injected. Second bean is "myService" and it is auto-wired by type which will result in the "personRepository" (from the data access application context loaded above) being injected. Third bean is the "transactionInterceptor" that also has a @SpringAdvice annotation defining the pointcut for where it will be applied. This is defined using AspectJ pointcut syntax as any class or method annotated with the @Transactional annotation.
MyConfig.groovy
import org.springframework.beans.factory.annotation.*
import org.springframework.beans.factory.java.template.*
import org.springframework.transaction.*
import org.springframework.transaction.annotation.*
import org.springframework.transaction.interceptor.*
@Configuration
class MyConfig extends ConfigurationSupport {
@Bean
MyApp myApp() {
MyApp myApp = new MyApp()
myApp.service = myService()
return myApp
}
@Bean(autowire = Autowire.BY_TYPE)
MyService myService() {
MyServiceImpl myService = new MyServiceImpl()
}
@Bean
@SpringAdvice(
"execution(public * ((@org.springframework.transaction.annotation.Transactional *)+).*(..)) || \
execution(* *(..)) && @annotation(org.springframework.transaction.annotation.Transactional)")
TransactionInterceptor transactionInterceptor() {
TransactionInterceptor txInterceptor = new TransactionInterceptor(
(PlatformTransactionManager) getBean("transactionManager"),
new AnnotationTransactionAttributeSource())
}
}
OK, almost done, I promise. We just need a script to set up the classpath and run the application.
HIBERNATE_CLASSPATH=../hibernate/commons-collections-2.1.1.jar HIBERNATE_CLASSPATH=$HIBERNATE_CLASSPATH:../hibernate/cglib-nodep-2.1_3.jar HIBERNATE_CLASSPATH=$HIBERNATE_CLASSPATH:../hibernate/dom4j-1.6.1.jar HIBERNATE_CLASSPATH=$HIBERNATE_CLASSPATH:../hibernate/hibernate-annotations.jar HIBERNATE_CLASSPATH=$HIBERNATE_CLASSPATH:../hibernate/hibernate-entitymanager.jar HIBERNATE_CLASSPATH=$HIBERNATE_CLASSPATH:../hibernate/hibernate3.jar HIBERNATE_CLASSPATH=$HIBERNATE_CLASSPATH:../hibernate/javassist.jar HIBERNATE_CLASSPATH=$HIBERNATE_CLASSPATH:../hibernate/jboss-archive-browsing.jar HIBERNATE_CLASSPATH=$HIBERNATE_CLASSPATH:../hibernate/jta.jar HIBERNATE_CLASSPATH=$HIBERNATE_CLASSPATH:../hibernate/ejb3-persistence.jar HIBERNATE_CLASSPATH=$HIBERNATE_CLASSPATH:../hibernate/c3p0-0.9.0.4.jar APP_CLASSPATH=../lib/spring.jar APP_CLASSPATH=$APP_CLASSPATH:../lib/spring-javaconfig.jar APP_CLASSPATH=$APP_CLASSPATH:../lib/aspectjweaver.jar APP_CLASSPATH=$APP_CLASSPATH:../lib/aspectjrt.jar APP_CLASSPATH=$APP_CLASSPATH:../lib/h2.jar groovy -cp $APP_CLASSPATH:$HIBERNATE_CLASSPATH:. MyAppStart
NOTE: Since Hibernate depends on CGLIB which depends on ASM 1.5.3 and Groovy ships with ASM 2.2 there is a bit of a conflict there. This is easiest solved by using the cglib-nodep-2.1_3.jar from the Spring distribution and leaving out the asm and cglib jars from the Hibernate distribution
Oh, and just to "prove" that this actually works - here are some of the log entries when I run this application:
Groovy 1.1-SNAPSHOT
2007-04-01 00:46:50,246 INFO [org.springframework.core.CollectionFactory] - JDK 1.4+ collections available
2007-04-01 00:46:50,279 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - Loading XML bean definitions from class path resource [spring-data-access.xml]
...
2007-04-01 00:46:50,777 INFO [org.springframework.context.support.ClassPathXmlApplicationContext] - 7 beans defined in application context [org.springframework.context.support.ClassPathXmlApplicationContext;hashCode=1688215]
2007-04-01 00:46:50,861 INFO [org.springframework.beans.factory.config.PropertyPlaceholderConfigurer] - Loading properties file from class path resource [jdbc.properties]
...
2007-04-01 00:46:51,196 INFO [org.hibernate.ejb.Version] - Hibernate EntityManager 3.2.1.GA
2007-04-01 00:46:51,208 INFO [org.hibernate.cfg.annotations.Version] - Hibernate Annotations 3.2.1.GA
2007-04-01 00:46:51,212 INFO [org.hibernate.cfg.Environment] - Hibernate 3.2.2
2007-04-01 00:46:51,215 INFO [org.hibernate.cfg.Environment] - hibernate.properties not found
2007-04-01 00:46:51,216 INFO [org.hibernate.cfg.Environment] - Bytecode provider name : cglib
2007-04-01 00:46:51,221 INFO [org.hibernate.cfg.Environment] - using JDK 1.4 java.sql.Timestamp handling
2007-04-01 00:46:51,287 INFO [org.hibernate.ejb.Ejb3Configuration] - Processing PersistenceUnitInfo [
name: test
...]
2007-04-01 00:46:51,319 INFO [org.hibernate.cfg.Configuration] - Reading mappings from resource : META-INF/orm.xml
2007-04-01 00:46:51,320 INFO [org.hibernate.ejb.Ejb3Configuration] - [PersistenceUnit: test] no META-INF/orm.xml found
2007-04-01 00:46:51,356 INFO [org.hibernate.cfg.AnnotationBinder] - Binding entity from annotated class: Person
2007-04-01 00:46:51,381 INFO [org.hibernate.cfg.annotations.EntityBinder] - Bind entity Person on table Person
2007-04-01 00:46:51,476 INFO [org.hibernate.connection.ConnectionProviderFactory] - Initializing connection provider: org.hibernate.ejb.connection.InjectedDataSourceConnectionProvider
2007-04-01 00:46:51,480 INFO [org.hibernate.ejb.connection.InjectedDataSourceConnectionProvider] - Using provided datasource
...
2007-04-01 00:46:51,663 INFO [org.hibernate.cfg.SettingsFactory] - RDBMS: H2, version: 1.0 (2007-01-30)
2007-04-01 00:46:51,663 INFO [org.hibernate.cfg.SettingsFactory] - JDBC driver: H2 JDBC Driver, version: 1.0 (2007-01-30)
2007-04-01 00:46:51,674 INFO [org.hibernate.dialect.Dialect] - Using dialect: org.hibernate.dialect.H2Dialect
...
2007-04-01 00:46:51,997 INFO [org.hibernate.tool.hbm2ddl.TableMetadata] - table found: KICKSTART.PUBLIC.PERSON
2007-04-01 00:46:51,997 INFO [org.hibernate.tool.hbm2ddl.TableMetadata] - columns: [name, id]
2007-04-01 00:46:51,997 INFO [org.hibernate.tool.hbm2ddl.TableMetadata] - foreign keys: []
2007-04-01 00:46:51,997 INFO [org.hibernate.tool.hbm2ddl.TableMetadata] - indexes: [primary_key_1]
2007-04-01 00:46:51,997 INFO [org.hibernate.tool.hbm2ddl.SchemaUpdate] - schema update complete
...
2007-04-01 00:46:52,397 INFO [org.springframework.context.java.AnnotationApplicationContext] - 4 beans defined in application context [org.springframework.context.java.AnnotationApplicationContext;hashCode=4136327]
...
2007-04-01 00:46:52,865 DEBUG [org.springframework.transaction.support.TransactionSynchronizationManager] - Initializing transaction synchronization
2007-04-01 00:46:52,866 DEBUG [org.springframework.transaction.interceptor.TransactionInterceptor] - Getting transaction for [MyServiceImpl.addAPerson]
...
2007-04-01 00:46:52,893 DEBUG [org.hibernate.SQL] - insert into Person (id, name) values (null, ?)
...
2007-04-01 00:46:52,937 DEBUG [org.springframework.transaction.interceptor.TransactionInterceptor] - Completing transaction for [MyServiceImpl.addAPerson]
2007-04-01 00:46:52,940 DEBUG [org.springframework.transaction.support.TransactionSynchronizationManager] - Clearing transaction synchronizationAbout Thomas Risberg
Thomas has been a developer on the Spring Framework project since early 2003, contributing to enhancements of the JDBC framework portion.
Thomas currently works as a consultant for SpringSource specializing in Java EE and database projects. He has been involved with developing database applications, both as a DBA and as an application developer for over 20 years, using a wide variety of languages and databases.
Thomas is co-author of "Professional Java Development with the Spring Framework" together with Rod Johnson, Juergen Hoeller, Alef Arendsen, and Colin Sampaleanu, published by Wrox in 2005.
More About Thomas »NFJS, the Magazine
2011-10-01 00:00:00.0 Issue Now AvailablePlugging into Gradle Plugins
by Tim BerglundEnterprise Integration Agility
by Jeremy DeaneRelax with Couch DB
by Johnny WeySass...CSS Evolved
by Mark Volkmann