Under the Hood: Using Spring in Grails

  • View
    4.601

  • Download
    2

  • Category

    Sports

Preview:

DESCRIPTION

Talk on using Spring in Grails given at SpringOne 2GX and the Groovy & Grails Exchange in 2012

Citation preview

© 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 grails­app/services

7CONFIDENTIAL 7CONFIDENTIAL

Grails Services

What is a Grails Service?

•Groovy class in grails­app/services

•Great place for business logic

8CONFIDENTIAL 8CONFIDENTIAL

Grails Services

What is a Grails Service?

•Groovy class in grails­app/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 grails­app/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 grails­app/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 (cloud­foundry, 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 pre­2.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. per­environment 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

First­class support in Grails via .properties files in grails­app/i18n

All generated controllers, GSPs, and layouts are fully 

internationalized with no hard­coded 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

Thread­local 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

Thread­local holders

Spring uses ThreadLocal scope since web requests are handled

per-thread

68CONFIDENTIAL 68CONFIDENTIAL

Thread­local holders

See org.springframework.transaction.support. 

TransactionSynchronizationManager and

org.springframework.orm.hibernate3. 

SessionFactoryUtils helper classes

69CONFIDENTIAL 69CONFIDENTIAL

Thread­local holders

Further helped by

org.codehaus.groovy.grails.orm.hibernate.support. 

GrailsOpenSessionInViewInterceptor

70CONFIDENTIAL 70CONFIDENTIAL

Thread­local 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

Thread­local holders

Hibernate implementation of GORM uses a

org.springframework.orm.hibernate3. 

HibernateTemplate under the hood to execute most queries

72CONFIDENTIAL 72CONFIDENTIAL

Thread­local holders

HibernateTemplate uses SessionFactoryUtils.getSession() to

find or create a Session

73CONFIDENTIAL 73CONFIDENTIAL

Thread­local 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 HTTP­based 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

Recommended