Under the Hood: Using Spring in Grails

5,929 views

Published on

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

Published in: Sports

Under the Hood: Using Spring in Grails

  1. 1. Under the Hood: Using Spring in GrailsBurt BeckwithSpringSource CONFIDENTIAL © 2010 SpringSource, A division of VMware. All rights reserved
  2. 2. 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 CONFIDENTIAL 2
  3. 3. 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 CONFIDENTIAL 3
  4. 4. Grails Services CONFIDENTIAL 4
  5. 5. Grails Services What is a Grails Service? CONFIDENTIAL 5
  6. 6. Grails Services What is a Grails Service? • Groovy class in grails­app/services CONFIDENTIAL 6
  7. 7. Grails Services What is a Grails Service? • Groovy class in grails­app/services • Great place for business logic CONFIDENTIAL 7
  8. 8. 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 CONFIDENTIAL 8
  9. 9. 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 CONFIDENTIAL 9
  10. 10. 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 CONFIDENTIAL 10
  11. 11. Manually Wiring CONFIDENTIAL 11
  12. 12. Grails Services src/java/test/UserManager.java 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; } } CONFIDENTIAL 12
  13. 13. Grails Services grails-app/conf/spring/resources.groovy import test.UserManager beans = { userService(UserManager) { loggingService = ref(loggingService) } } CONFIDENTIAL 13
  14. 14. Grails Services grails-app/conf/spring/resources.groovy import test.UserManager beans = { userService(UserManager) { bean -> bean.autowire = byName } } CONFIDENTIAL 14
  15. 15. How to Make it Transactional? CONFIDENTIAL 15
  16. 16. Grails Services grails-app/conf/spring/resources.groovyimport org.codehaus.groovy.grails.orm.support.GroovyAwareNamedTransactionAttributeSourceimport org.springframework.transaction.interceptor.TransactionProxyFactoryBeanimport test.UserManagerbeans = { 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) }} CONFIDENTIAL 16
  17. 17. orCONFIDENTIAL 17
  18. 18. Grails Services src/java/test/UserManager.java package test; import org.springframework.transaction.annotation.Transactional; import other.LoggingService; import auth.User; @Transactional public class UserManager { private LoggingService loggingService; public void setLoggingService(LoggingService service) { loggingService = service; } public User createUser(String username) { ... } } CONFIDENTIAL 18
  19. 19. Grails Services grails-app/conf/spring/resources.groovyimport test.UserManagerbeans = { userService(UserManager) { bean -> bean.lazyInit = true bean.autowire = byName }} CONFIDENTIAL 19
  20. 20. How to be sure its  Transactional? CONFIDENTIAL 20
  21. 21. Grails Services src/java/test/UserManager.javapackage 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"); } ... }} CONFIDENTIAL 21
  22. 22. Grails Services grails-app/services/test/UserService.groovy 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 } } CONFIDENTIAL 22
  23. 23. Transaction Helper Methods CONFIDENTIAL 23
  24. 24. Utility Methods import o.s.t.interceptor.TransactionAspectSupport import o.s.t.support.TransactionSynchronizationManager for (sc in grailsApplication.serviceClasses) {    def metaClass = sc.clazz.metaClass    … } CONFIDENTIAL 24
  25. 25. Utility Methods // returns TransactionStatus metaClass.getCurrentTransactionStatus = { ­> if (!delegate.isTransactionActive()) { return null } TransactionAspectSupport.currentTransactionStatus() } CONFIDENTIAL 25
  26. 26. Utility Methods // void, throws NoTransactionException metaClass.setRollbackOnly = { ­> TransactionAspectSupport.currentTransactionStatus()          .setRollbackOnly() } CONFIDENTIAL 26
  27. 27. Utility Methods // returns boolean metaClass.isRollbackOnly = { ­> if (!delegate.isTransactionActive()) { return false } delegate.getCurrentTransactionStatus().isRollbackOnly() } CONFIDENTIAL 27
  28. 28. Utility Methods // returns boolean metaClass.isTransactionActive = { ­> TransactionSynchronizationManager          .isSynchronizationActive() } CONFIDENTIAL 28
  29. 29. Dependency Injection CONFIDENTIAL 29
  30. 30. Inversion of Control and Dependency Injection def userService CONFIDENTIAL 30
  31. 31. Inversion of Control and Dependency Injection def userService private Object userService void setUserService(Object userService) {    this.userService = userService } Object getUserService() {    return userService } CONFIDENTIAL 31
  32. 32. Complex dependency configuration using Spring  SpEL CONFIDENTIAL 32
  33. 33. Complex dependency configuration using Spring SpEL beans = {    bar(Bar)    foo(Foo) {       name = #{bar.name}    } } CONFIDENTIAL 33
  34. 34. Complex dependency configuration using Spring SpEL beans = {    bar(Bar)    foo(Foo) {       name = #{bar.resourceName()}    } } CONFIDENTIAL 34
  35. 35. Manually injecting dependencies at runtime CONFIDENTIAL 35
  36. 36. Manually injecting dependencies at runtime import org.springframework.beans.factory.config.AutowireCapableBeanFactory ... def grailsApplication ... def instance = new XXX(...) def ctx = grailsApplication.mainContext ctx.beanFactory.autowireBeanProperties(    instance,    AutowireCapableBeanFactory.AUTOWIRE_BY_NAME,    false) CONFIDENTIAL 36
  37. 37. Bean Scopes CONFIDENTIAL 37
  38. 38. Bean Scopes By default Spring beans are singletons CONFIDENTIAL 38
  39. 39. Bean Scopes Other scopes • prototype • session • request CONFIDENTIAL 39
  40. 40. Bean Scopes You can even create your own custom scope: • implement  org.springframework.beans.factory.config.Scope • register: ctx.beanFactory.registerScope  myScope, new MyScope() CONFIDENTIAL 40
  41. 41. Bean lifecycles and interfaces CONFIDENTIAL 41
  42. 42. 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 } CONFIDENTIAL 42
  43. 43. 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 CONFIDENTIAL 43
  44. 44. Bean lifecycles and interfaces package com.mycompany.myapp import org.springframework.beans.factory.DisposableBean import org.springframework.beans.factory.InitializingBean class LdapAuthenticationManager implements  InitializingBean, DisposableBean {    ...    void afterPropertiesSet() {       // initialization work    }    void destroy() {       // shutdown work    } } CONFIDENTIAL 44
  45. 45. Bean PostProcessors CONFIDENTIAL 45
  46. 46. Bean PostProcessors o.s.b.factory.config.BeanPostProcessor • Object postProcessBeforeInitialization(Object bean,  String beanName) • Object postProcessAfterInitialization(Object bean,  String beanName) CONFIDENTIAL 46
  47. 47. Bean PostProcessors o.s.b.factory.config.BeanFactoryPostProcessor • void postProcessBeanFactory(ConfigurableListableBeanFactory  beanFactory) CONFIDENTIAL 47
  48. 48. Bean PostProcessors o.s.b.factory.support.BeanDefinitionRegistryPostProcessor • extends BeanFactoryPostProcessor • void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry  registry) CONFIDENTIAL 48
  49. 49. Cloud Support Plugin (cloud­foundry, heroku) dataSourceBean.driverClassName =  updatedValues.driverClassName dataSourceBean.url = updatedValues.url + suffix dataSourceBean.username = updatedValues.userName dataSourceBean.password = updatedValues.password CONFIDENTIAL 49
  50. 50. 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 apps resources.groovy CONFIDENTIAL 50
  51. 51. Bean Aliases CONFIDENTIAL 51
  52. 52. 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 } CONFIDENTIAL 52
  53. 53. 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 CONFIDENTIAL 53
  54. 54. 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 } CONFIDENTIAL 54
  55. 55. Internationalization CONFIDENTIAL 55
  56. 56. 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) CONFIDENTIAL 56
  57. 57. Internationalization Use the <g:message> tag in GSPs, message method in  controllers Or use dependency injection like with any Spring bean • def messageSource CONFIDENTIAL 57
  58. 58. Spring MVC Controllers CONFIDENTIAL 58
  59. 59. 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] CONFIDENTIAL 59
  60. 60. 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())); } CONFIDENTIAL 60
  61. 61. Spring MVC @RequestMapping URI value must end in .dispatch Add entries in UrlMappings to create more natural URLs class UrlMappings {    static mappings = {       …       "/mvc/hello"(uri:"/mvc/hello.dispatch")       "/mvc/other"(uri:"/mvc/other.dispatch")    } } CONFIDENTIAL 61
  62. 62. Spring MVC Use @Autowired for dependency injection (on fields in Groovy  classes, on setters or constructors in Java) private GrailsApplication grailsApplication; @Autowired public void setGrailsApplication(GrailsApplication app) {    grailsApplication = app; } CONFIDENTIAL 62
  63. 63. Hibernate Integration CONFIDENTIAL 63
  64. 64. Hibernate Integration org.springframework.orm.hibernate3.  LocalSessionFactoryBean factory bean configures  org.hibernate.SessionFactory instances CONFIDENTIAL 64
  65. 65. Hibernate Integration org.springframework.orm.hibernate3.  HibernateTransactionManager implements  org.springframework.transaction.  PlatformTransactionManager to abstract away the details of  transaction management CONFIDENTIAL 65
  66. 66. 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 CONFIDENTIAL 66
  67. 67. Thread­local holders Spring uses ThreadLocal scope since web requests are handled per-thread CONFIDENTIAL 67
  68. 68. Thread­local holders See org.springframework.transaction.support.  TransactionSynchronizationManager and org.springframework.orm.hibernate3.  SessionFactoryUtils helper classes CONFIDENTIAL 68
  69. 69. Thread­local holders Further helped by org.codehaus.groovy.grails.orm.hibernate.support.  GrailsOpenSessionInViewInterceptor CONFIDENTIAL 69
  70. 70. 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 CONFIDENTIAL 70
  71. 71. Thread­local holders Hibernate implementation of GORM uses a org.springframework.orm.hibernate3.  HibernateTemplate under the hood to execute most queries CONFIDENTIAL 71
  72. 72. Thread­local holders HibernateTemplate uses SessionFactoryUtils.getSession() to find or create a Session CONFIDENTIAL 72
  73. 73. Thread­local holders Plugins that enable asynchronous processing (Quartz, Gpars, Executor) all implement patterns similar to OpenSessionInView CONFIDENTIAL 73
  74. 74. Other Cool Stuff CONFIDENTIAL 74
  75. 75. 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 CONFIDENTIAL 75
  76. 76. Other Cool Stuff Remoting (see the Grails remoting plugin) • Remote Method Invocation (RMI) • Hessian (Cauchos lightweight binary HTTP­based protocol) • Burlap (another protocol from Caucho which uses XML) • Springs HTTP invoker JMX (see the Grails jmx plugin) Email (see the Grails mail plugin) CONFIDENTIAL 76
  77. 77. Other Cool Stuff Other persistence support • HibernateTemplate • JdbcTemplate • Other database support • JDO • JPA • iBATIS (2.x) SQL Maps CONFIDENTIAL 77
  78. 78. Want More Information? CONFIDENTIAL 78
  79. 79. Thank You CONFIDENTIAL 80

×