Eliminating Development Redeploys using Gradle
by Matt CholickFor service development, my team recently moved away from Grails to the Dropwizard framework. One of the things I really missed from the Grails stack, though, was auto-reloading: any changes to source files appear in the running app moments after saving, without a restart. It proved feasible to pull this functionality into Gradle builds as well.
Spring Loaded is the library that Grails uses under its hood. It supports reloading quite a few types of changes without restarting the JVM:
- Add/modify/delete methods/fields/constructors
- Change annotations on types/methods/fields/constructors
- Add/remove/change values in enum types
The other piece I needed was a watch plugin: something to trigger Gradle tasks when source files change.
For the full working example, clone my demo Github repository.
The first piece of setup is adding an additional configuration. This isolates the spring-loaded.jar (which is only needed during development) from the standard configurations such as compile:
configurations {
agent
}
The dependency block reads as follows:
configurations {
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.3.4'
compile 'io.dropwizard:dropwizard-core:0.7.1'
compile 'com.sun.jersey:jersey-client:1.18'
agent "org.springframework:springloaded:${springloadedVersion}"
}
The compile dependencies are the standard set one would expect in a Dropwizard project. The line starting with "agent" adds the Spring Loaded dependency to the agent configuration defined earlier. The build script uses this dependency to get the spring-loaded.jar onto the file system. springloadedVersion is a constant defined earlier in the build file.
task copyAgent(type: Copy) {
from configurations.agent
into "$buildDir/agent"
}
run.mustRunAfter copyAgent
The above copyAgent task will take the spring-loaded.jar file and copy it to the build directory for later use as a javaagent. run is also configured to follow copyAgent in the chain.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.bluepapa32:gradle-watch-plugin:0.1.3'
}
}
apply plugin: 'watch'
watch {
groovy {
files files('src/main/groovy')
tasks 'compileGroovy'
}
}
task watchThread() << {
Thread.start {
project.tasks.watch.execute()
}
}
run.mustRunAfter watchThread
The above script block adds and configures watch. The buildscript block adds the proper repository and the the watch plugin as a dependency. The watch block configures the plugin; whenever there are changes in src/main/groovy, the Groovy source will be recompiled. The watchThread task executes watch parallely. This is needed because the final job will execute two tasks which both run continuously: watch and run. watch would normally block run. Finally, the run task is configured to follow watchThread when both are part of the chain.
run {
args = ['server', 'app.yaml']
jvmArgs = ["-javaagent:${new File("$buildDir/agent/springloaded-${springloadedVersion}.jar").absolutePath}", '-noverify']
}
task reloading(dependsOn: [watchThread, copyAgent, run])
This final bit of code configures the run command with a javaagent flag. This tells the JVM to use Spring Loaded and let it do its magic. Spring Loaded also needs the noverify flag. The reloading task is the actual task to run during development. It strings the tasks to copy the agent, spin up a thread watching for source changes, and running Dropwizard's main method.
This configuration structure would also support frameworks outside of Dropwizard: anything with a main method, really. Though it doesn't work for all kinds of code changes, it can eliminate a great many application restarts during development.