How to hot reload the resources file when the changes happened in Grails.
I was working on Grails 3.3.0 and hot reloading was not working while changing in the resources files. So, I found some workaround which is worth sharing here. There are the following steps to resolve the issue.
- Configure to watch the directory.
- Watch the directory for code changes
- Reload spring resources config
- Load from the application
Configure to watch the directory:
Here I am creating a class BeanWatcher.groovy to watch the config file changes.
I want to watch the config spring file so I provided the path: "grails-app/conf/spring" under the project directory. As we are running in the thread so the current thread will not be interrupted for each time file changes. Here, we are registering directories and sub-directories.
import grails.spring.BeanBuilder
import grails.util.Environment
import grails.util.Holders
import groovy.util.logging.Slf4j
import org.grails.core.exceptions.GrailsConfigurationException
import org.grails.spring.DefaultRuntimeSpringConfiguration
import org.grails.spring.RuntimeSpringConfigUtilities
import org.springframework.beans.factory.support.BeanDefinitionRegistry
import java.nio.file.FileSystems
import java.nio.file.FileVisitResult
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.SimpleFileVisitor
import java.nio.file.StandardWatchEventKinds
import java.nio.file.WatchEvent
import java.nio.file.WatchKey
import java.nio.file.WatchService
import java.nio.file.attribute.BasicFileAttributes
import java.util.concurrent.atomic.AtomicBoolean
class BeanWatcher extends Thread{
private final WatchService watchService
private long sleepTime = 1000
private AtomicBoolean stop = new AtomicBoolean(false)
public BeanWatcher(Path path){
watchService = FileSystems.getDefault().newWatchService()
walkAndRegisterDirectories(path)
}
private void walkAndRegisterDirectories(final Path start){
// register directory and sub-directories
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs){
registerDirectory(dir)
return FileVisitResult.CONTINUE
}
})
}
private void registerDirectory(dir){
dir.register(
watchService,
StandardWatchEventKinds.ENTRY_MODIFY)
}
@Override
void run() {
}
static void configureBeanWatcher(){
Environment environment = Environment.current
File baseDir = new File(environment.getReloadLocation()).canonicalFile
String location = baseDir.canonicalPath
File watchDir = new File(location, "grails-app/conf/spring")
Path path = watchDir.toPath()
BeanWatcher beanWatcher = new BeanWatcher(path)
beanWatcher.start()
}
}
Watch the directory for code changes:
@Override
void run() {
try {
WatchKey key
try {
while ((key = watchService.take()) != null) {
List<WatchEvent <?>> watchEvents = key.pollEvents()
for (WatchEvent <?> event : watchEvents) {
WatchEvent.Kind <?> kind = event.kind()
WatchEvent <Path> pathWatchEvent = cast(event)
Path name = pathWatchEvent.context()
Path dir = (Path) key.watchable()
Path child = dir.resolve(name).toAbsolutePath()
File childFile = child.toFile()
if(kind == StandardWatchEventKinds.ENTRY_MODIFY){
onChange(childFile)
}
}
key.reset()
}
} catch (InterruptedException e) {
e.printStackTrace()
}
} catch (IOException e) {
e.printStackTrace()
}
}
@SuppressWarnings("unchecked")
private static <T> WatchEvent<T> cast(WatchEvent<?> event) {
return (WatchEvent<T>)event
}
The above code will listen to the file changes and call the method onChange for each time the file changes. Now, it's time to reload our resources file while changes occurred.
Reload spring resources config:
private static void onChange(File file) { // the changed file
processBeanDefinitionRegistry()
}
public static void processBeanDefinitionRegistry(){
def springConfig = new DefaultRuntimeSpringConfiguration()
def application = Holders.grailsApplication
def context = application.mainContext
def beanResources = context.getResource(RuntimeSpringConfigUtilities.SPRING_RESOURCES_GROOVY)
if (beanResources?.exists()) {
def gcl = new GroovyClassLoader(application.classLoader)
try {
RuntimeSpringConfigUtilities.reloadSpringResourcesConfig(springConfig, application, gcl.parseClass(new GroovyCodeSource(beanResources.URL)))
} catch (Throwable e) {
throw new GrailsConfigurationException("Error loading spring/resources.groovy file: ${e.message}", e)
}
}
def bb = new BeanBuilder(null, springConfig, application.classLoader)
bb.registerBeans((BeanDefinitionRegistry)application.getMainContext())
}
This is the code snippet that I found in the grails where they used to reload the file. This will reload and re-configure the resources file.
Load from the application:
We set up all the necessary config and necessary code inside BeanWatcher.groovy now lets load the file from application for this add the following code.
Application.groovy
if (Environment.current == Environment.DEVELOPMENT){
BeanWatcher.configureBeanWatcher()
}
This is only for the development env so we did the same.
0 comments:
New comments are not allowed.