Configuration en temps réel grâce à Java NIO

Oracle ayant récemment publié la version 7 du JRE, j'ai décidé de commencer à tester quelque peu les nouveautés disponibles.

Dans cet article, je vais présenter les possibilités offertes par la nouvelle gestion du système de fichiers, avec java.nio.file.WatchService, qui permet de mettre en place un outil de "surveillance" des ressources.

Imaginons donc une fonctionnalité de mise à jour en temps réel de la configuration d'une application lors de la modification d'un fichier properties.

Voici le code mis en place :

package com.developpef;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
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.List;
import java.util.Properties;

public class PropertiesSpreader {

	private static final String basePath = "C:\\java\\ides\\eclipse-jee-helios-SR2\\workspace\\Test\\resources";
	private static Path propDir;

	public static void main(String[] args) {
		try {
			loadProperties();

			FileSystem fs = FileSystems.getDefault();
			final WatchService watcher = fs.newWatchService();
			propDir = fs.getPath(basePath);
			propDir.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);

			Thread t = new Thread(new Runnable() {
				@Override
				public void run() {
					watch(watcher);
				}
			});
			t.start();

			System.out.println("Le watcher a été démarré...");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private static void watch(WatchService watcher) {
		WatchKey key = null;
		do {
			try {
				key = watcher.take();
				List<WatchEvent<?>> events = key.pollEvents();
				for (WatchEvent<?> event : events) {
					if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
						Path propFile = (Path) event.context();
						if (propFile.endsWith("test.properties")) {

							BasicFileAttributes bfa = Files.readAttributes(
									propDir.resolve(propFile),
									BasicFileAttributes.class);
							System.out
									.println("\n** Fichier propriétés modifié! Rechargement... (modifications du "
											+ bfa.lastModifiedTime() + ")**");
							loadProperties();
						}
					}
				}
			} catch (InterruptedException|IOException e) {
				e.printStackTrace();
			}
		} while (key.reset());
	}

	private static void loadProperties() throws IOException {
		InputStream stream = new FileInputStream(new File(basePath
				+ "\\test.properties"));
		Properties props = new Properties();
		props.load(stream);
		stream.close();
		System.out.println("Titre de l'application : "
				+ props.getProperty("App.title"));
	}

}

L'intérêt de cette implémentation est de gérer la configuration dans un thread autonome (comme un daemon) qui actualisera les propriétés de l'application.

La gestion du système de fichiers a été "allégée", notamment grâce à un nouveau "super objet" java.nio.file.FileSystem qui facilite la manipulation des ressources de manière logique. Ainsi, par exemple, plus besoin d'instancier des java.io.File à tour de bras : la méthode java.nio.file.FileSystem.getPath("Path") permet de récupérer n'importe quel type de chemin, qu'il s'agisse d'un fichier ou d'un dossier, sous la forme d'un java.nio.file.Path.

Ensuite, l'essentiel de la fonction de mise à jour réside dans la méthode watch qui relance le chargement du fichier de configuration dès que celui-ci est modifié. Exemple de trace lors de l'utilisation :

Titre de l'application : Java 7 Watch Service
Le watcher a été démarré...

** Fichier propriétés modifié! Rechargement... (modifications du 2011-08-10T09:22:37.183499Z)**
Titre de l'application : Java 7 Watch Service rocks!!

Pour les plus observateurs, vous aurez également pu remarquer une nouvelle syntaxe de catch multiple : catch (InterruptedException|IOException e) apportée par le projet Coin dont j'ai parlé dans cet article.

Note :

Ce code fonctionne correctement lors de la modification du fichier avec Notepad (de Windows). Mais dès que l'on essaie de modifier le fichier avec un autre éditeur (Notepad++ ou Eclipse), le WatchService réagit à deux évènements de modification. Je n'ai pas encore d'explication quant à ce comportement (comme expliqué ici), mais si quelqu'un a déjà pu approfondir les tests et trouvé une solution, tout commentaire est le bienvenu!


Fichier(s) joint(s) :