Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I force a grails dev server to reload when any code recompiles?

The short question is: can I force the Grails 2.2 dev server to reload (reinitializing all singleton services, etc) whenever new code is hot-compiled and swapped in?

Let me make the question clear with an example. Let's say that

  1. I have a singleton-scoped service called MyService. In its @PostConstruct function, it initializes a new object of type Helper (and holds on to the reference).

  2. I run-app in dev mode, test things out, and then...

  3. I edit the source code for Helper.groovy

What happens next is:

  • Grails automatically recompiles Helper.groovy
  • Grails swaps the new code into the running dev server
  • But the new Helper code isn't called because the already-instantiated MyService object already has a handle on an instance of the old kind.

My current workaround is to keep a dependency graph in my head, and touch or trivially modify MyService every time I change one of its dependencies. But I'd much rather force the dev server to reload completely whenever new code is swapped in.

So... is it possible to force the dev server to reload whenever any code changes?

And a bonus question: when I change a Domain Object, the only way I can force the dev server to "follow" those changes is to stop-app, clean, and run-app manually. Can I automate this to happen whenever it's required?

Thanks!

like image 694
Bosh Avatar asked Aug 19 '13 20:08

Bosh


1 Answers

CRITICAL UPDATE

I was able to get what you were looking for. Laying it down step wise:

REQUIREMENT -
When Helper class is modified, service class should be refreshed to refer to the modified helper class.

SETUP

//src/groovy
class SampleHelper {
    String name = 'Heisenberg'

    def sayMyName(){
        "$name"
    }
}

//grails-app/service
import javax.annotation.PostConstruct
class SampleService {
    String cachedName

    @PostConstruct
    def initIt(){
        cachedName = new SampleHelper().sayMyName()
    }

    def serviceMethod() {
        cachedName
    }
}

//controller
class SampleController {
    def sampleService

    def index() {
        render sampleService.serviceMethod()
    }
}

PROBLEM STATEMENT
When name in helper class is updated to Gus, the class refreshes but the service class is still referencing to the old instance of SampleHelper. Result, name still shows Heisenberg.

SOLUTION

  • Watch the helper class.
  • Refresh the service class onChange of helper class.

This can be achieved by using the Pluginator plugin in the application which gives the flexibility to watch a file and do something onChange event.

  • Install the plugin.
  • Add the below ApplicationPlugin.groovy under grails-app/conf.

as

class ApplicationPlugin {
    //Watch the helper class
    def watchedResources = "file:./src/groovy/**/*.groovy"

    //Re-Register the bean (service class) onChange of Helper Class
    //This can be generalized more.
    def onChange = { event ->
        if (event.source) {
            def beans = beans {
                sampleService(SampleService) { bean ->
                    bean.autowire =  true
                }
            }
            if (event.ctx) {
                event.ctx.registerBeanDefinition(
                        "sampleService",
                        beans.getBeanDefinition("sampleService"))
            }
        }
    }
}
  • PROBLEM STATEMENT should be fixed after the above steps followed.

NOTE

  • This process might be resource intensive if followed for all classes and artefacts.

OLD & Valid Answer

I am unable to recreate your problem in Grails 2.2.0. Below is the setup I have (correct me if I am wrong anywhere):

//src/groovy
class MyHelper{
    def sayMyName(){
        "Heisenberg"
    }
}

//service
import javax.annotation.PostConstruct
class MyService {
    def myHelper

    @PostConstruct
    def initIt(){
        myHelper = new MyHelper()
    }

    def serviceMethod() {
        myHelper.sayMyName()
    }
}

//controller (to test)
class MyController {
    def myService

    def index() {
        render myService.serviceMethod()
    }
}

Steps:

  • Run the initial setup. (run-app)
  • Hit the controller to see "Heisenberg"
  • Modify MyHelper.sayMyName() to return "Walter White" instead of "Heisenberg"
  • Hit the controller again and see "Walter White"

Observation:

  • I would rather make MyHelper a bean and use (inject) it in the service class for use.

Entry to resources.groovy as follows:

beans = {
    myHelper(com.example.MyHelper)
}

Service class becomes:

class MyService {
    def myHelper

    def serviceMethod() {
        myHelper.sayMyName()
    }
}
  • Now, referring to field in MyHelper in the service class will give an issue because the bean has been instantiated already in the container

Regarding Bonus Question:

I am unable to recreate the problem either in Grails 2.2.0

//domain
class MyDomain {
    String name
}

//controller action
def add(){
    def myDomain = new MyDomain(name: 'Testing').save(flush: true)
    render myDomain.properties
}
  • Change the domain, add String email
  • Save Domain class.
  • Modify action to add email in MyDomain (obviously a new row will be created).
  • Save Controller.
  • Hit the action again.
  • See the result.
like image 134
dmahapatro Avatar answered Sep 21 '22 14:09

dmahapatro