1. Giriş
WatchService — Java NIO-nun (New IO) bir hissəsidir və fayl sistemindəki dəyişiklikləri real vaxtda izləməyə imkan verir. Onu qovluqlar üçün siqnalizasiya kimi təsəvvür etmək olar: kimsə fayl əlavə etsə, silsə və ya dəyişsə — dərhal bildiriş alacaqsınız. Bu imkan Java 7 ilə NIO.2 birlikdə ortaya çıxdı, ondan əvvəl isə tərtibatçılar ya qovluğu əl ilə sorğulamalı (polling) olurdu, ya da üçüncü tərəf kitabxanalarından istifadə edirdilər.
WatchService-in praktik tətbiqləri çoxşaxəlidir: o, yeni faylları avtomatik emal etməyə, jurnallar aparmağa və ehtiyat nüsxələr yaratmağa, qovluqları server və ya buludla sinxronlaşdırmağa, eləcə də konfiqurasiya fayllarındakı dəyişiklikləri izləməyə kömək edir.
İzləmək üçün qovluqların qeydiyyatı
İzləməyə başlamaq üçün:
- WatchService nümunəsini əldə edin.
- Lazım olan qovluğu qeydiyyata salın və hansı hadisələrin maraqlı olduğunu göstərin.
WatchService-i əldə edirik
import java.nio.file.*;
WatchService watchService = FileSystems.getDefault().newWatchService();
Qovluğu qeydiyyatdan keçiririk
Qeydiyyat üçün Path obyektindəki register metodundan istifadə edirik:
Path dir = Paths.get("data/uploads");
dir.register(
watchService,
StandardWatchEventKinds.ENTRY_CREATE, // fayl/qovluq yaradılması
StandardWatchEventKinds.ENTRY_DELETE, // fayl/qovluğun silinməsi
StandardWatchEventKinds.ENTRY_MODIFY // fayl/qovluqda dəyişiklik
);
İzah:
- ENTRY_CREATE — kimsə nəsə əlavə edib.
- ENTRY_DELETE — kimsə nəyisə silib.
- ENTRY_MODIFY — kimsə faylı dəyişib (məsələn, mətn əlavə edib).
Vacib! WatchService bir anda yalnız bir qovluğu izləyir (alt qovluqlar olmadan). Bütün ierarxiyanı izləmək istəyirsinizsə — hər alt qovluğu ayrıca qeydiyyata salmalısınız.
2. Hadisələrin emalı: gözləmə dövrü
Artıq izləməni quraşdırdığımıza görə (daha çox «müşahidəçi qonşu» kimidir, «Böyük Qardaş» ruhunda deyil), hadisələri gözləyə bilərik. WatchService «hadisə növbəsi» nümunəsini reallaşdırır: nəsə baş verən kimi hadisə növbəyə qoyulur.
Əsas dövr
while (true) {
// Hadisələri gözləyirik (bloklayan çağırış)
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
// Hadisənin tipi: yaradılma, silinmə, dəyişmə
WatchEvent.Kind<?> kind = event.kind();
// Fayl/qovluq adı (Path, izlənən qovluğa nisbidir)
Path filename = (Path) event.context();
if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
System.out.println("Fayl/qovluq yaradıldı: " + filename);
} else if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
System.out.println("Fayl/qovluq silindi: " + filename);
} else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
System.out.println("Fayl/qovluq dəyişdirildi: " + filename);
}
}
// Mütləq açarı sıfırlamaq lazımdır, yoxsa izləmə dayanar!
boolean valid = key.reset();
if (!valid) {
break; // Qovluq əlçatan deyil, çıxırıq
}
}
Bu necə işləyir?
- WatchService.take() — hadisə baş verənədək axını bloklayır (bloklanmayan rejim üçün poll() istifadə etmək olar).
- key.pollEvents() — yığılıb qalan bütün hadisələrin siyahısı.
- event.context() — dəyişdirilmiş faylın və ya qovluğun adı (izlənən direktoriyaya nisbətən).
- Hadisələr emal edildikdən sonra mütləq key.reset() çağırın. Qovluq silinibsə və ya əlçatan deyilsə, reset() false qaytaracaq — dövrü bitirmək olar.
Tam nümunə: "data/uploads" qovluğunu izləyirik
Gəlin tədris tətbiqimizə yükləmələr qovluğu üçün sadə «siqnalizasiya» əlavə edək:
import java.nio.file.*;
import java.io.IOException;
public class WatcherDemo {
public static void main(String[] args) throws IOException, InterruptedException {
Path dir = Paths.get("data/uploads");
if (!Files.exists(dir)) {
Files.createDirectories(dir);
}
WatchService watchService = FileSystems.getDefault().newWatchService();
dir.register(
watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY
);
System.out.println("Qovluğun izlənməsi " + dir.toAbsolutePath());
while (true) {
WatchKey key = watchService.take(); // hadisələri gözləyirik
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
Path filename = (Path) event.context();
System.out.printf("[%s] %s\n", kind.name(), filename);
}
boolean valid = key.reset();
if (!valid) {
System.out.println("Qovluq əlçatan deyil, izləmə başa çatdı.");
break;
}
}
}
}
Yoxlayın: Bu kodu işə salın və "data/uploads" qovluğunda fayl yaradın, silin və ya dəyişdirin. Proqram dərhal reaksiya verəcək!
3. WatchService məhdudiyyətləri və xüsusiyyətləri
Yalnız bir qovluq, alt qovluqlar olmadan
WatchService yalnız qeydiyyatdan keçirdiyiniz qovluğu izləyir. Əgər onun daxilində alt qovluqlar varsa, onların içindəki dəyişikliklər görünməyəcək — hər alt qovluğu ayrıca qeydiyyata salmaq lazımdır.
Nə etməli?
Bütün ierarxiyanı izləmək istəyirsinizsə, bütün alt qovluqları gəzib onları bir-bir qeydiyyatdan keçirməlisiniz. Məsələn, yeni alt qovluq yaradıldıqda — onu da dərhal qeydiyyata salın.
Müxtəlif OS-lərdə xüsusiyyətlər
Windows: WatchService kifayət qədər stabil işləyir, amma bəzən bir neçə hadisəni birinə «birləşdirə» bilər (məsələn, böyük fayl köçürüləndə).
Linux/macOS: Reallaşdırma sistem mexanizmlərinə (inotify, kqueue) əsaslanır. Bəzən hadisələr gecikmə ilə gələ bilər və ya əksinə, çox sayda ola bilər (məsələn, hər yadda saxlama zamanı ENTRY_MODIFY).
Yalnız ad üzrə hadisələr
WatchService yalnız dəyişən obyektin adını (izlənən qovluğa nisbətən) bildirir, lakin faylın daxilində nə dəyişdiyi barədə tam məlumat vermir. Dəqiq nə dəyişdiyini bilmək lazımdırsa — faylı özünüz oxuyun.
Yüklənmə zamanı hadisələrin itməsi
Qısa müddətdə qovluqda həddən artıq çox dəyişiklik baş verərsə (məsələn, minlərlə faylın kütləvi köçürülməsi), hadisə növbəsi dolub bir hissəsi itə bilər. Kritik tapşırıqlar üçün əlavə yoxlamalar tətbiq etmək məsləhətdir.
4. Praktik nümunələr
Yeni faylların avtomatik emalı
Tutaq ki, "photos/incoming" qovluğunda yaranan yeni şəkilləri avtomatik emal etməli olan proqram yazırsınız.
Path dir = Paths.get("photos/incoming");
WatchService watchService = FileSystems.getDefault().newWatchService();
dir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE);
while (true) {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
Path filename = (Path) event.context();
if (filename.toString().endsWith(".jpg")) {
System.out.println("Yeni foto: " + filename);
// Burada emal əlavə etmək olar: kopyalama, sıxma, analiz və s.
}
}
}
key.reset();
}
Sadə dəyişiklik jurnalının reallaşdırılması
Bütün hadisələri ayrıca log-fayla yazmaq olar:
import java.nio.file.*;
import java.io.*;
import java.time.LocalDateTime;
public class SimpleLogger {
public static void main(String[] args) throws IOException, InterruptedException {
Path dir = Paths.get("logs/monitored");
Files.createDirectories(dir);
Path logFile = Paths.get("logs/changes.log");
try (BufferedWriter writer = Files.newBufferedWriter(logFile, StandardOpenOption.CREATE, StandardOpenOption.APPEND)) {
WatchService watchService = FileSystems.getDefault().newWatchService();
dir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE);
System.out.println("İzləmə: " + dir);
while (true) {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
String log = String.format("%s [%s] %s\n",
LocalDateTime.now(), event.kind().name(), event.context());
writer.write(log);
writer.flush();
System.out.print(log);
}
key.reset();
}
}
}
}
Yeni alt qovluqların yaradılmasının izlənməsi (və qeydiyyatı)
İzlənən qovluqda yeni alt qovluq yaradılarsa, onu dərhal növbəti monitorinq üçün qeydiyyata salmaq olar:
if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
Path createdPath = dir.resolve((Path) event.context());
if (Files.isDirectory(createdPath)) {
createdPath.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
System.out.println("Yeni alt qovluğun izlənməsinə başlandı: " + createdPath);
}
}
5. Vacib nüanslar və tipik səhvlər
Səhv №1: key.reset() çağırmağı unutmaq. Hadisələr emal edildikdən sonra açarı sıfırlamasanız, qovluğun izlənməsi dayanacaq və artıq heç bir hadisə almayacaqsınız. Bu, yeni başlayanlar üçün klassik «tələ»dir: hər şey işləyirmiş kimi görünür, sonra — bam! — proqram sükuta bürünür.
Səhv №2: istisnaların nəzərə alınmaması. Fayl sistemi ilə iş həmişə gözlənilməzliklərlə doludur: qovluq silinə bilər, disk — ayrılabilir, icazələr — dəyişə bilər. İstisnalar (IOException, ClosedWatchServiceException) işlənməsə, proqram dayana bilər.
Səhv №3: yalnız bir qovluğu izləmək. Bir çoxları düşünür ki, qovluğu qeydiyyata saldıqda bütün iç qovluqlar da izlənəcək. Belə deyil! Bütün strukturu izləmək lazımdırsa — rekursiv qeydiyyatı reallaşdırın.
Səhv №4: əsas axının bloklanması. WatchService.take() hadisə yarananadək axını bloklayır. Əsas axın başqa işlər də görməlidirsə, izləməni ayrıca axında başladın.
Səhv №5: yüksək yüklənmə zamanı hadisələrin itməsi. Qovluqda çoxlu dəyişiklik baş verərsə, hadisə növbəsi dolub bir hissə itə bilər. Kritik tətbiqlər üçün qovluğun vəziyyətini periodik yoxlamaq məqsədəuyğundur (məsələn, hər dəqiqə fayl siyahısını müqayisə etmək).
Səhv №6: nisbi yolların yanlış emalı. event.context() fayl adını izlənən qovluğa nisbətən qaytarır. Absolyut yol lazımdırsa — dir.resolve((Path) event.context()) istifadə edin.
GO TO FULL VERSION