under the hood: using spring in grails
DESCRIPTION
Talk on using Spring in Grails given at SpringOne 2GX and the Groovy & Grails Exchange in 2012TRANSCRIPT
© 2010 SpringSource, A division of VMware. All rights reserved
CONFIDENTIALCONFIDENTIAL
Under the Hood: Using Spring in Grails
Burt Beckwith
SpringSource
2CONFIDENTIAL 2CONFIDENTIAL
Who Am I
Java developer for over 13 years
Background in Spring, Hibernate, Spring Security
Grails developer for 5 years
SpringSource employee on the Grails team
Created or reworked over 40 Grails plugins
http://burtbeckwith.com/blog/
https://twitter.com/#!/burtbeckwith
3CONFIDENTIAL 3CONFIDENTIAL
Spring Overview
Main functions of Spring
• Bean container: ApplicationContext and BeanFactory
• Dependency Injection (DI) and Inversion of Control (IoC)
• Proxies
• Transactions
• Security
• Caching
• Event publishing and listening
• Exception conversion
4CONFIDENTIAL 4CONFIDENTIAL
Grails Services
5CONFIDENTIAL 5CONFIDENTIAL
Grails Services
What is a Grails Service?
6CONFIDENTIAL 6CONFIDENTIAL
Grails Services
What is a Grails Service?
•Groovy class in grailsapp/services
7CONFIDENTIAL 7CONFIDENTIAL
Grails Services
What is a Grails Service?
•Groovy class in grailsapp/services
•Great place for business logic
8CONFIDENTIAL 8CONFIDENTIAL
Grails Services
What is a Grails Service?
•Groovy class in grailsapp/services
•Great place for business logic
•A Spring bean, by default singleton scope
9CONFIDENTIAL 9CONFIDENTIAL
Grails Services
What is a Grails Service?
•Groovy class in grailsapp/services
•Great place for business logic
•A Spring bean, by default singleton scope
•Automatically transactional unless configured otherwise
10CONFIDENTIAL 10CONFIDENTIAL
Grails Services
What is a Grails Service?
•Groovy class in grailsapp/services
•Great place for business logic
•A Spring bean, by default singleton scope
•Automatically transactional unless configured otherwise
•Proxied, sometimes multiple times
11CONFIDENTIAL 11CONFIDENTIAL
Manually Wiring
12CONFIDENTIAL 12CONFIDENTIAL
Grails Services
package test;
import other.LoggingService;import auth.User;
public class UserManager {
private LoggingService loggingService;
public void setLoggingService(LoggingService service) { loggingService = service; }
public User createUser(String username) { User user = new User(); user.setUsername(username); user.save(); loggingService.logMessage( "Created user with username " + username); return user; }}
src/java/test/UserManager.java
13CONFIDENTIAL 13CONFIDENTIAL
Grails Services
import test.UserManager
beans = { userService(UserManager) { loggingService = ref('loggingService') }}
grails-app/conf/spring/resources.groovy
14CONFIDENTIAL 14CONFIDENTIAL
Grails Services
import test.UserManager
beans = { userService(UserManager) { bean -> bean.autowire = 'byName' }}
grails-app/conf/spring/resources.groovy
15CONFIDENTIAL 15CONFIDENTIAL
How to Make it Transactional?
16CONFIDENTIAL 16CONFIDENTIAL
Grails Services
import org.codehaus.groovy.grails.orm.support.GroovyAwareNamedTransactionAttributeSourceimport org.springframework.transaction.interceptor.TransactionProxyFactoryBean
import test.UserManager
beans = {
userService(TransactionProxyFactoryBean) { bean -> bean.lazyInit = true
target = { innerBean -> innerBean.lazyInit = true innerBean.autowire = 'byName' innerBean.beanClass = UserManager }
proxyTargetClass = true
def props = ['*': 'PROPAGATION_REQUIRED'] as Properties TransactionAttributeSource = new GroovyAwareNamedTransactionAttributeSource(transactionalAttributes: props)
transactionManager = ref('transactionManager') }}
grails-app/conf/spring/resources.groovy
17CONFIDENTIAL 17CONFIDENTIAL
or
18CONFIDENTIAL 18CONFIDENTIAL
Grails Services
package test;
import org.springframework.transaction.annotation.Transactional;import other.LoggingService;import auth.User;
@Transactionalpublic class UserManager {
private LoggingService loggingService;
public void setLoggingService(LoggingService service) { loggingService = service; }
public User createUser(String username) { ... }}
src/java/test/UserManager.java
19CONFIDENTIAL 19CONFIDENTIAL
Grails Services
import test.UserManager
beans = {
userService(UserManager) { bean -> bean.lazyInit = true bean.autowire = 'byName' }}
grails-app/conf/spring/resources.groovy
20CONFIDENTIAL 20CONFIDENTIAL
How to be sure it's Transactional?
21CONFIDENTIAL 21CONFIDENTIAL
Grails Services
package test;
import org.springframework.transaction.TransactionStatus;import org.springframework.transaction.annotation.Transactional;import org.springframework.transaction.interceptor.TransactionAspectSupport;import org.springframework.transaction.support.TransactionSynchronizationManager;
...
public User createUser(String username) {
if (TransactionSynchronizationManager.isSynchronizationActive()) { TransactionStatus status = TransactionAspectSupport.currentTransactionStatus(); System.out.println("TX is active; isRollbackOnly: " + status.isRollbackOnly()); } else { System.out.println("Uh-oh, TX not active"); }
... }}
src/java/test/UserManager.java
22CONFIDENTIAL 22CONFIDENTIAL
Grails Services
package test
import auth.User
class UserService {
def loggingService
User createUser(String username) { def user = new User(username: username) user.save() loggingService.logMessage( "Created user with username $username") user }}
grails-app/services/test/UserService.groovy
23CONFIDENTIAL 23CONFIDENTIAL
Transaction Helper Methods
24CONFIDENTIAL 24CONFIDENTIAL
Utility Methods
import o.s.t.interceptor.TransactionAspectSupportimport o.s.t.support.TransactionSynchronizationManager
for (sc in grailsApplication.serviceClasses) { def metaClass = sc.clazz.metaClass
…}
25CONFIDENTIAL 25CONFIDENTIAL
Utility Methods
// returns TransactionStatusmetaClass.getCurrentTransactionStatus = { >
if (!delegate.isTransactionActive()) {return null
}TransactionAspectSupport.currentTransactionStatus()
}
26CONFIDENTIAL 26CONFIDENTIAL
Utility Methods
// void, throws NoTransactionExceptionmetaClass.setRollbackOnly = { >
TransactionAspectSupport.currentTransactionStatus() .setRollbackOnly()}
27CONFIDENTIAL 27CONFIDENTIAL
Utility Methods
// returns booleanmetaClass.isRollbackOnly = { >
if (!delegate.isTransactionActive()) {return false
}delegate.getCurrentTransactionStatus().isRollbackOnly()
}
28CONFIDENTIAL 28CONFIDENTIAL
Utility Methods
// returns booleanmetaClass.isTransactionActive = { >
TransactionSynchronizationManager .isSynchronizationActive()}
29CONFIDENTIAL 29CONFIDENTIAL
Dependency Injection
30CONFIDENTIAL 30CONFIDENTIAL
Inversion of Control and Dependency Injection
def userService
31CONFIDENTIAL 31CONFIDENTIAL
Inversion of Control and Dependency Injection
def userService
private Object userService
void setUserService(Object userService) { this.userService = userService}
Object getUserService() { return userService}
32CONFIDENTIAL 32CONFIDENTIAL
Complex dependency configuration using Spring
SpEL
33CONFIDENTIAL 33CONFIDENTIAL
Complex dependency configuration using Spring SpEL
beans = { bar(Bar)
foo(Foo) { name = '#{bar.name}' }}
34CONFIDENTIAL 34CONFIDENTIAL
Complex dependency configuration using Spring SpEL
beans = { bar(Bar)
foo(Foo) { name = '#{bar.resourceName()}' }}
35CONFIDENTIAL 35CONFIDENTIAL
Manually injecting dependencies at runtime
36CONFIDENTIAL 36CONFIDENTIAL
Manually injecting dependencies at runtime
import org.springframework.beans.factory.config.AutowireCapableBeanFactory
...
def grailsApplication
...
def instance = new XXX(...)
def ctx = grailsApplication.mainContextctx.beanFactory.autowireBeanProperties( instance, AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, false)
37CONFIDENTIAL 37CONFIDENTIAL
Bean Scopes
38CONFIDENTIAL 38CONFIDENTIAL
Bean Scopes
By default Spring beans are singletons
39CONFIDENTIAL 39CONFIDENTIAL
Bean Scopes
Other scopes
• prototype
• session
• request
40CONFIDENTIAL 40CONFIDENTIAL
Bean Scopes
You can even create your own custom scope:
• implement
org.springframework.beans.factory.config.Scope
• register: ctx.beanFactory.registerScope
'myScope', new MyScope()
41CONFIDENTIAL 41CONFIDENTIAL
Bean lifecycles and interfaces
42CONFIDENTIAL 42CONFIDENTIAL
Bean lifecycles and interfaces
Set the initMethod and/or the destroyMethod names
when registering beans
import com.mycompany.myapp.LdapAuthenticationManager
...
authenticationManager(LdapAuthenticationManager) { bean > serverUrl = '...' password = '...' bean.initMethod = 'init' bean.destroyMethod = 'destroy'}
43CONFIDENTIAL 43CONFIDENTIAL
Bean lifecycles and interfaces
Implement the
org.springframework.beans.factory.InitializingBean
interface and its afterPropertiesSet method and/or the
org.springframework.beans.factory.DisposableBean
interface and its destroy method
44CONFIDENTIAL 44CONFIDENTIAL
Bean lifecycles and interfaces
package com.mycompany.myapp
import org.springframework.beans.factory.DisposableBeanimport org.springframework.beans.factory.InitializingBean
class LdapAuthenticationManager implements InitializingBean, DisposableBean {
...
void afterPropertiesSet() { // initialization work }
void destroy() { // shutdown work }}
45CONFIDENTIAL 45CONFIDENTIAL
Bean PostProcessors
46CONFIDENTIAL 46CONFIDENTIAL
Bean PostProcessors
o.s.b.factory.config.BeanPostProcessor
• Object postProcessBeforeInitialization(Object bean,
String beanName)
• Object postProcessAfterInitialization(Object bean,
String beanName)
47CONFIDENTIAL 47CONFIDENTIAL
Bean PostProcessors
o.s.b.factory.config.BeanFactoryPostProcessor
• void postProcessBeanFactory(ConfigurableListableBeanFactory
beanFactory)
48CONFIDENTIAL 48CONFIDENTIAL
Bean PostProcessors
o.s.b.factory.support.BeanDefinitionRegistryPostProcessor
• extends BeanFactoryPostProcessor
• void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry
registry)
49CONFIDENTIAL 49CONFIDENTIAL
Cloud Support Plugin (cloudfoundry, heroku)
dataSourceBean.driverClassName = updatedValues.driverClassName
dataSourceBean.url = updatedValues.url + suffix
dataSourceBean.username = updatedValues.userName
dataSourceBean.password = updatedValues.password
50CONFIDENTIAL 50CONFIDENTIAL
Alternate approach to BeanDefinition modification
def doWithSpring = {
def mybeanDef = delegate.getBeanDefinition('mybean')
mybeanDef.beanClass = NewClass
mybeanDef.propertyValues.add("order", application.config.plugin?.rendering?.order ?: 42)}
● Use loadAfter = ['plugin1', 'plugin2'] to
ensure the bean is loaded
● Only valid in a plugin, not the app's resources.groovy
51CONFIDENTIAL 51CONFIDENTIAL
Bean Aliases
52CONFIDENTIAL 52CONFIDENTIAL
Aliases
As of Grails 2.1 aliases work fully
• You can create aliases pre2.1 but only if defined in the same
resources.groovy or plugin (doWithSpring)
beans = { springConfig.addAlias 'alias', 'realBean'}
53CONFIDENTIAL 53CONFIDENTIAL
Aliases
The cache plugin registers the alias cacheOperationSource for
the bean registered as
org.springframework.cache.annotation.AnnotationCacheOperationSource#0
Can use to have multiple implementations of a bean and choose
one via configuration at startup, e.g. perenvironment or some
other rule
54CONFIDENTIAL 54CONFIDENTIAL
Aliases
import grails.util.Environment
beans = {
String realBeanName
switch (Environment.current) {
case Environment.TEST: realBeanName = 'testCardProcessingService'; break
case Environment.PRODUCTION: realBeanName = 'productionCardProcessingService'; break
default: // Environment.DEVELOPMENT, custom envs realBeanName = 'mockCardProcessingService'; break }
springConfig.addAlias 'cardProcessingService', realBeanName}
55CONFIDENTIAL 55CONFIDENTIAL
Internationalization
56CONFIDENTIAL 56CONFIDENTIAL
Internationalization
Firstclass support in Grails via .properties files in grailsapp/i18n
All generated controllers, GSPs, and layouts are fully
internationalized with no hardcoded strings
Domain class validation errors are internationalized the same way
Enabled under the hood by the messageSource bean
(org.springframework.context.MessageSource)
57CONFIDENTIAL 57CONFIDENTIAL
Internationalization
Use the <g:message> tag in GSPs, message method in
controllers
Or use dependency injection like with any Spring bean
• def messageSource
58CONFIDENTIAL 58CONFIDENTIAL
Spring MVC Controllers
59CONFIDENTIAL 59CONFIDENTIAL
Spring MVC
New in Grails 1.2
Annotate src/java or src/groovy classes with @Controller
Add all packages to the grails.spring.bean.packages list in
Config.groovy
• e.g.grails.spring.bean.packages = ['gr8conf.testapp.foo']
60CONFIDENTIAL 60CONFIDENTIAL
Spring MVC
Annotate methods with
@o.s.w.bind.annotation.RequestMapping
@RequestMapping("/mvc/hello.dispatch")public ModelMap handleRequest() { return new ModelMap() .addAttribute("text", "some text") .addAttribute("cost", 42) .addAttribute("config", grailsApplication.getConfig().flatten()));}
61CONFIDENTIAL 61CONFIDENTIAL
Spring MVC
class UrlMappings {
static mappings = { …
"/mvc/hello"(uri:"/mvc/hello.dispatch")
"/mvc/other"(uri:"/mvc/other.dispatch") }}
@RequestMapping URI value must end in .dispatch
Add entries in UrlMappings to create more natural URLs
62CONFIDENTIAL 62CONFIDENTIAL
Spring MVC
Use @Autowired for dependency injection (on fields in Groovy
classes, on setters or constructors in Java)
private GrailsApplication grailsApplication;
@Autowiredpublic void setGrailsApplication(GrailsApplication app) { grailsApplication = app;}
63CONFIDENTIAL 63CONFIDENTIAL
Hibernate Integration
64CONFIDENTIAL 64CONFIDENTIAL
Hibernate Integration
org.springframework.orm.hibernate3.
LocalSessionFactoryBean factory bean configures
org.hibernate.SessionFactory instances
65CONFIDENTIAL 65CONFIDENTIAL
Hibernate Integration
org.springframework.orm.hibernate3.
HibernateTransactionManager implements
org.springframework.transaction.
PlatformTransactionManager to abstract away the details of
transaction management
66CONFIDENTIAL 66CONFIDENTIAL
Threadlocal holders
Cumbersome to explicitly open a Hibernate Session or start a
transaction and have to pass one or more related objects from
method to method
67CONFIDENTIAL 67CONFIDENTIAL
Threadlocal holders
Spring uses ThreadLocal scope since web requests are handled
per-thread
68CONFIDENTIAL 68CONFIDENTIAL
Threadlocal holders
See org.springframework.transaction.support.
TransactionSynchronizationManager and
org.springframework.orm.hibernate3.
SessionFactoryUtils helper classes
69CONFIDENTIAL 69CONFIDENTIAL
Threadlocal holders
Further helped by
org.codehaus.groovy.grails.orm.hibernate.support.
GrailsOpenSessionInViewInterceptor
70CONFIDENTIAL 70CONFIDENTIAL
Threadlocal holders
Opens a Hibernate Session at the start of all controller requests
and registers it in thread-local scope
For the duration of the request, there is always an active session
After the request it flushes and closes the Session
71CONFIDENTIAL 71CONFIDENTIAL
Threadlocal holders
Hibernate implementation of GORM uses a
org.springframework.orm.hibernate3.
HibernateTemplate under the hood to execute most queries
72CONFIDENTIAL 72CONFIDENTIAL
Threadlocal holders
HibernateTemplate uses SessionFactoryUtils.getSession() to
find or create a Session
73CONFIDENTIAL 73CONFIDENTIAL
Threadlocal holders
Plugins that enable asynchronous processing (Quartz, Gpars,
Executor) all implement patterns similar to OpenSessionInView
74CONFIDENTIAL 74CONFIDENTIAL
Other Cool Stuff
75CONFIDENTIAL 75CONFIDENTIAL
Other Cool Stuff
Standard and Custom Events
Resources
Data Binding and Validation
Marshalling XML using O/X Mappers
JMS (see Grails jms and ActiveMQ plugins)
EJBs
76CONFIDENTIAL 76CONFIDENTIAL
Other Cool Stuff
Remoting (see the Grails remoting plugin)
• Remote Method Invocation (RMI)
• Hessian (Caucho's lightweight binary HTTPbased protocol)
• Burlap (another protocol from Caucho which uses XML)
• Spring's HTTP invoker
JMX (see the Grails jmx plugin)
Email (see the Grails mail plugin)
77CONFIDENTIAL 77CONFIDENTIAL
Other Cool Stuff
Other persistence support
• HibernateTemplate
• JdbcTemplate
• Other database support
• JDO
• JPA
• iBATIS (2.x) SQL Maps
78CONFIDENTIAL 78CONFIDENTIAL
Want More Information?
80CONFIDENTIAL 80CONFIDENTIAL
Thank You