Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Load spring beans from custom groovy files in grails app

Trying to load spring beans from custom groovy file in Grails 2.3.7. I know this question has been asked before, but after hours of searching, I'm unable to find a consistent approach that loads from the classpath.

The Goal

  • Modularize resources.groovy into multiple custom resource files
  • Put custom resource files in standard location: grails-app/conf/spring
  • Use plugin to do the magic; minimize overhead

Tried...

//## grails-app/conf/spring/MyBeansConfig.groovy 
beans {
   testsvc(TestService){
      msg = 'hello'
   }
}

Note above, I'm using beans {}, not beans = {}, apparently it makes a difference:

resources.groovy

This works...

beans = {
  loadBeans 'file:C:\\Proj\Test1\grails-app\\conf\\spring\\MyBeansConfig.groovy'
}

...and according to docs, this should too, but doesn't:

importBeans("classpath:*MyBeansConfig.groovy")

UPDATE - WORKING OPTIONS

(working for Grails 2.3.7)

Option 1: (src/java)

Following lukelazarovic advice, this approach works since Grails automatically copies (not compiles) groovy files in src/java to the classpath; works fine in eclipse and with war deployment:

//bean config files here
src/java/MyBeansConfig.groovy
src/java/FooBeansConfig.groovy

//resources.groovy
loadBeans('classpath*:*BeansConfig.groovy')

Options 2: (grails-app/conf/spring)

This approach allows for custom bean config files in grails-app/conf/spring (credits to ideascultor and mark.esher)

   //bean config files here
   grails-app/conf/spring/MyBeansConfig.groovy

   //## resources.groovy
   //for eclipse/local testing
   loadBeans('file:./grails-app/conf/spring/*BeansConfig.groovy')
   //for WAR/classpath 
   loadBeans('classpath*:*BeansConfig.groovy')


   //## BuildConfig.groovy
   grails.war.resources = { stagingDir, args ->
      copy(todir: "${stagingDir}/WEB-INF/classes/spring") {
         fileset(dir:"grails-app/conf/spring") {
            include(name: "*BeansConfig.groovy")
            exclude(name: "resources.groovy")        
            exclude(name: "resources.xml")
         }
      }
   }

Options 3: (Custom Plugin)

If you're using custom plugins, this approach is ideal; boiler plate config gets refactored into the plugin:

Plugin Config

   //## scripts/_Events.groovy
   eventCreateWarStart = { warName, stagingDir ->
      def libDir = new File("${stagingDir}/WEB-INF/classes/spring")

      ant.copy(todir: libDir) {
         fileset(dir:"grails-app/conf/spring") {
            include(name: "*BeansConfig.groovy")
            exclude(name: "resources.groovy")
            exclude(name: "resources.xml")
         }
      }
   }

   //## MyGrailsPlugin.groovy
   def doWithSpring = {       
      loadBeans('file:./grails-app/conf/spring/*BeansConfig.groovy')
      loadBeans('classpath*:*BeansConfig.groovy') 
   }

Grails App

No config!...just create your bean config files using the *BeansConfig.groovy convention, good to go!

//bean config files here
grails-app/conf/spring/MyBeansConfig.groovy

Update 9/24/2015

Found some issues with option 3:

  • loading of duplicate resource files
  • spring resources not configured correctly for test-app

We managed to fix the above issue such that any resource files in grails-app/conf/spring work the same when executing Grails in eclipse, WAR, test-app, etc.

First step: we created a class to have more control over locating and loading resource files; this was necessary as some files were being loaded more than once due to cross-referenced beans. We're using a plugin to encapsulate this functionality, so this class lives in that plugin:

class CustomResourceLoader {
   static loadSpringBeans(BeanBuilder bb){         
         def files = ['*'] //load all resource files 
         //match resources using multiple methods
         def matchedResourceList = []
         files.each {
            matchedResourceList +=
               getPatternResolvedResources("file:./grails-app/conf/spring/" + it + ".groovy").toList()
            matchedResourceList +=
               getPathMatchedResources("classpath*:spring/" + it + ".groovy").toList()
         }

         //eliminate duplicates resource loaded from recursive reference
         def resourceMap = [:]       
         matchedResourceList.each{
            if(it) resourceMap.put(it.getFilename(), it) 
         }           
         //make resources.groovy first in list
         def resourcesFile = resourceMap.remove("resources.groovy")
         if(!resourcesFile)
            throw new RuntimeException("resources.groovy was not found where expected!")

         def resourcesToLoad = [resourcesFile]
         resourceMap.each{k,v -> resourcesToLoad << v }         
         log.debug("Spring resource files about to load: ")        
         resourcesToLoad.each{ bb.loadBeans(it) }
   }

   static def getPatternResolvedResources(path){
      ResourcePatternResolver resourcePatternResolver =
         new PathMatchingResourcePatternResolver();
      return resourcePatternResolver.getResources(path);
   }

   static def getPathMatchedResources(path){
      return new PathMatchingResourcePatternResolver().getResources(path)
   }
}

Removed BeansConfig.groovy suffix; WAR generation now picks up any resources in grails-app/conf/spring

plugin, _Events.groovy
eventCreateWarStart = { warName, stagingDir ->
      def libDir = new File("${stagingDir}/WEB-INF/classes/spring")
      ant.copy(todir: libDir) {
         fileset(dir:"grails-app/conf/spring") {
            include(name: "*.groovy")          
            exclude(name: "resources.xml")
         }
      }
   }
}

In the plugin definition file, call the loader from doWithSpring(), passing BeanBuilder (the delegate) as the argument (very important):

def doWithSpring = {       
   CustomResourceLoader.loadSpringBeans(delegate)
}

That's it, there is no requirement on users of the plugin aside from creating custom resource files in grails-app/conf/spring

like image 593
raffian Avatar asked Jun 04 '14 03:06

raffian


2 Answers

I had a similar problem just a few days ago, with a groovy configuration file that I added into grails-app/conf. While this works with other resources (they are copied and available on the classpath), the problem with the groovy file was simply that it was compiled and the class file was included, i.e. not the groovy file itself.

I didn't find any good documentation on how this should be done and finally just added it to web-app/WEB-INF/classes. Groovy files placed here will be copied (not compiled) and available on the classpath.

like image 75
Steinar Avatar answered Oct 10 '22 10:10

Steinar


I had the same problem with custom XML files in Grails 2.1.2.

Having XML resources in grails-app/conf/spring didn't work in production environment AFAIR.

To make it working both in development and production environments I finally put the resources into src/java. I think you can achieve the same result by putting your groovy files into src/groovy.

like image 1
lukelazarovic Avatar answered Oct 10 '22 09:10

lukelazarovic