Пакет java.nio.file предоставляет API уведомления об изменении файла, который называется API службы наблюдения . Это позволяет нам зарегистрировать папку в службе часов. При регистрации мы сообщаем сервису, какие типы событий нас интересуют: создание файла, изменение файла или удаление файла.
Вам также могут понравиться: Java IO и NIO
Когда служба обнаруживает интересующее событие, оно направляется зарегистрированному процессу и обрабатывается по мере необходимости. Вот как это работает:
- Первым шагом является создание нового
WatchService
с использованиемnewWatchService()
методаFileSystem
класса. - Затем мы регистрируем
Path
экземпляр для отслеживаемой папки с типами интересующих нас событий. - И наконец, мы реализуем бесконечный цикл для ожидания входящих событий. Когда происходит событие, ключ сигнализируется и помещается в очередь наблюдателя. После обработки его событий нам нужно вернуть его в
ready
состояние, вызвав егоreset()
метод. Если он возвращает false, ключ больше не действителен и цикл может завершиться.
Джава
1
WatchService watchService = Файловые системы . getDefault (). newWatchService ();
2
Путь Путь = Пути . get ( "c: \\ directory" );
3
путь . зарегистрироваться ( watchService , ENTRY_CREATE , ENTRY_MODIFY , ENTRY_DELETE );
4
логический опрос = true ;
5
while ( опрос ) {
6
WatchKey ключ = watchService . взять ();
7
для ( WatchEvent <?> событие : key . pollEvents ()) {
8
Система . из . println ( "Вид события:" + событие . вид () + "- Файл:" + событие . контекст ());
9
}
10
опрос = ключ . сброс ();
11
}
Это вывод консоли:
Джава
xxxxxxxxxx
1
Вид события : ENTRY_CREATE - Файл : файл . текст
2
Вид события : ENTRY_DELETE - Файл : файл . текст
3
Вид события : ENTRY_CREATE - Файл : тест . текст
4
Вид события : ENTRY_MODIFY - Файл : тест . текст
API WatchService находится на довольно низком уровне, что позволяет нам настраивать его. В этой статье и следуя шаблону Observer, мы собираемся разработать высокоуровневый API поверх этого механизма для прослушивания событий файла для данной папки. Мы начнем с создания FileEvent
класса, расширяющего класс, java.util.EventObject
из которого должны быть получены все объекты состояния события. FileEvent
Экземпляр построен с ссылкой на источник, который является логически файл , на котором произошло событие на.
FileEvent.java
Джава
xxxxxxxxxx
1
импорт Java . -й . Файл ;
2
импорт Java . Util . EventObject ;
3
открытый класс FileEvent расширяет EventObject {
5
public FileEvent ( Файловый файл ) {
7
супер ( файл );
8
}
9
public File getFile () {
11
return ( File ) getSource ();
12
}
13
}
Затем мы создаем FileListener
интерфейс, который должен быть реализован наблюдателем, чтобы получать уведомления о событиях файла. Он расширяет java.util.EventListener
интерфейс, который является интерфейсом тегирования, который должны расширять все интерфейсы прослушивателя событий.
FileListener.java
Джава
xxxxxxxxxx
1
импорт Java . Util . EventListener ;
2
открытый интерфейс FileListener расширяет EventListener {
4
public void onCreated ( событие FileEvent );
6
public void onModified ( событие FileEvent );
8
public void onDeleted ( событие FileEvent );
10
}
Последняя часть головоломки состоит в создании субъекта, который ведет список наблюдателей и уведомляет их о любых изменениях состояния, вызывая один из их методов. Мы назовем его FileWatcher
и дадим папку, вот как создается экземпляр этого класса.
Джава
xxxxxxxxxx
1
открытый класс FileWatcher {
2
защищенный список < FileListener > listeners = new ArrayList <> ();
4
защищенная итоговая папка File ;
5
public FileWatcher ( Папка файлов ) {
7
это . папка = папка ;
8
}
9
public List < FileListener > getListeners () {
11
вернуть слушателей ;
12
}
13
public FileWatcher setListeners ( Список < FileListener > слушателей ) {
15
это . слушатели = слушатели ;
16
верни это ;
17
}
18
}
Он может реализовать Runnable
интерфейс, поэтому мы можем запустить процесс наблюдения с потоком демона при вызове его watch()
метода, если папка существует.
Джава
xxxxxxxxxx
1
открытый класс FileWatcher реализует Runnable {
2
public void watch () {
4
if ( папка . существует ()) {
5
Тема потока = новая тема ( это );
6
нить . setDaemon ( true );
7
нить . начало ();
8
}
9
}
10
12
public void run () {
13
// реализация еще не предоставлена
14
}
15
}
При реализации его run()
метода создается WatchService
экземпляр для опроса событий в операторе try-with-resources. Мы будем следить за этим, используя статический финальный список в FileWatcher
классе, чтобы позже мы могли вызвать его close()
метод, чтобы заставить любой поток, ожидающий получения ключей, выбросить непроверенный ClosedWatchServiceException
, что будет прерывать процесс наблюдения чистым способом. Поэтому мы не получим предупреждений об утечке памяти, когда приложение будет корректно завершено.
Джава
xxxxxxxxxx
1
2
public void contextDestroyed ( событие ServletContextEvent ) {
3
для ( WatchService watchService : FileWatcher . getWatchServices ()) {
4
попытаться {
5
WatchService . закрыть ();
6
} catch ( IOException e ) {
7
}
8
}
9
}
Джава
xxxxxxxxxx
1
открытый класс FileWatcher реализует Runnable {
2
защищенный статический финал List < WatchService > watchServices = new ArrayList <> ();
4
6
public void run () {
7
try ( WatchService watchService = FileSystems . getDefault (). newWatchService ()) {
8
Путь Путь = Пути . get ( папка . getAbsolutePath ());
9
путь . зарегистрироваться ( watchService , ENTRY_CREATE , ENTRY_MODIFY , ENTRY_DELETE );
10
часыУслуги . добавить ( watchService );
11
логический опрос = true ;
12
while ( опрос ) {
13
poll = pollEvents ( watchService );
14
}
15
} catch ( IOException | InterruptedException | ClosedWatchServiceException e ) {
16
Автор . currentThread (). прерывание ();
17
}
18
}
19
защищенный логический pollEvents ( WatchService watchService ) выбрасывает InterruptedException {
21
WatchKey ключ = watchService . взять ();
22
Путь путь = ( путь ) ключ . смотрибельный ();
23
для ( WatchEvent <?> событие : key . pollEvents ()) {
24
notifyListeners ( событие . вид (), путь . разрешение (( путь ), событие . контекст ()). toFile ());
25
}
26
вернуться ключ . сброс ();
27
}
28
public static List < WatchService > getWatchServices () {
30
Вернуть Коллекции . unmodifiableList ( watchServices );
31
}
32
}
Всякий раз, когда происходит событие, путь к файлу разрешается, и слушатели уведомляются соответствующим образом. Если это создание новой папки, FileWatcher
для ее мониторинга будет создан другой экземпляр.
Джава
xxxxxxxxxx
1
открытый класс FileWatcher реализует Runnable {
2
protected void notifyListeners ( WatchEvent . Kind <?> kind , File file ) {
4
Событие FileEvent = новый FileEvent ( файл );
5
if ( kind == ENTRY_CREATE ) {
6
for ( слушатель FileListener : слушатели ) {
7
слушатель . onCreated ( событие );
8
}
9
if ( file . isDirectory ()) {
10
// создаем новый экземпляр FileWatcher для просмотра нового каталога
11
новый FileWatcher ( файл ). setListeners ( слушатели ). смотреть ();
12
}
13
}
14
иначе если ( kind == ENTRY_MODIFY ) {
15
for ( слушатель FileListener : слушатели ) {
16
слушатель . onModified ( событие );
17
}
18
}
19
иначе если ( kind == ENTRY_DELETE ) {
20
for ( слушатель FileListener : слушатели ) {
21
слушатель . onDeleted ( событие );
22
}
23
}
24
}
25
}
Вот полный список FileWatcher
класса.
FileWatcher.java
Джава
xxxxxxxxxx
1
импортировать статическую Java . Nio . файл . StandardWatchEventKinds . * ;
2
импорт Java . -й . Файл ;
3
импорт Java . -й . IOException ;
4
импорт Java . Nio . файл . ClosedWatchServiceException ;
5
импорт Java . Nio . файл . Файловые системы ;
6
импорт Java . Nio . файл . Путь ;
7
импорт Java . Nio . файл . Дорожки ;
8
импорт Java . Nio . файл . WatchEvent ;
9
импорт Java . Nio . файл . WatchKey ;
10
импорт Java . Nio . файл . WatchService ;
11
импорт Java . Util . ArrayList ;
12
импорт Java . Util . Коллекции ;
13
импорт Java . Util . Список ;
14
открытый класс FileWatcher реализует Runnable {
16
защищенный список < FileListener > listeners = new ArrayList <> ();
18
защищенная итоговая папка File ;
19
защищенный статический финал List < WatchService > watchServices = new ArrayList <> ();
20
21
public FileWatcher ( Папка файлов ) {
22
это . папка = папка ;
23
}
24
public void watch () {
26
if ( папка . существует ()) {
27
Тема потока = новая тема ( это );
28
нить . setDaemon ( true );
29
нить . начало ();
30
}
31
}
32
34
public void run () {
35
try ( WatchService watchService = FileSystems . getDefault (). newWatchService ()) {
36
Путь Путь = Пути . get ( папка . getAbsolutePath ());
37
путь . зарегистрироваться ( watchService , ENTRY_CREATE , ENTRY_MODIFY , ENTRY_DELETE );
38
часыУслуги . добавить ( watchService );
39
логический опрос = true ;
40
while ( опрос ) {
41
poll = pollEvents ( watchService );
42
}
43
} catch ( IOException | InterruptedException | ClosedWatchServiceException e ) {
44
Автор . currentThread (). прерывание ();
45
}
46
}
47
защищенный логический pollEvents ( WatchService watchService ) выбрасывает InterruptedException {
49
WatchKey ключ = watchService . взять ();
50
Путь путь = ( путь ) ключ . смотрибельный ();
51
для ( WatchEvent <?> событие : key . pollEvents ()) {
52
notifyListeners ( событие . вид (), путь . разрешение (( путь ), событие . контекст ()). toFile ());
53
}
54
вернуться ключ . сброс ();
55
}
56
protected void notifyListeners ( WatchEvent . Kind <?> kind , File file ) {
58
Событие FileEvent = новый FileEvent ( файл );
59
if ( kind == ENTRY_CREATE ) {
60
for ( слушатель FileListener : слушатели ) {
61
слушатель . onCreated ( событие );
62
}
63
if ( file . isDirectory ()) {
64
новый FileWatcher ( файл ). setListeners ( слушатели ). смотреть ();
65
}
66
}
67
иначе если ( kind == ENTRY_MODIFY ) {
68
for ( слушатель FileListener : слушатели ) {
69
слушатель . onModified ( событие );
70
}
71
}
72
иначе если ( kind == ENTRY_DELETE ) {
73
for ( слушатель FileListener : слушатели ) {
74
слушатель . onDeleted ( событие );
75
}
76
}
77
}
78
public FileWatcher addListener ( слушатель FileListener ) {
80
слушатели . добавить ( слушатель );
81
верни это ;
82
}
83
public FileWatcher removeListener ( слушатель FileListener ) {
85
слушатели . удалить ( слушатель );
86
верни это ;
87
}
88
public List < FileListener > getListeners () {
90
вернуть слушателей ;
91
}
92
public FileWatcher setListeners ( Список < FileListener > слушателей ) {
94
это . слушатели = слушатели ;
95
верни это ;
96
}
97
public static List < WatchService > getWatchServices () {
99
Вернуть Коллекции . unmodifiableList ( watchServices );
100
}
101
}
Последним штрихом нашего дизайна может быть создание FileAdapter
класса, который обеспечивает реализацию FileListener
интерфейса по умолчанию, так что мы можем обработать только несколько событий для сохранения кода.
FileAdapter.java
Джава
xxxxxxxxxx
1
открытый абстрактный класс FileAdapter реализует FileListener {
2
4
public void onCreated ( событие FileEvent ) {
5
// реализация не предоставлена
6
}
7
9
public void onModified ( событие FileEvent ) {
10
// реализация не предоставлена
11
}
12
14
public void onDeleted ( событие FileEvent ) {
15
// реализация не предоставлена
16
}
17
}
FileAdapter
Класс очень полезен в моем случае, чтобы перезагрузить сценарий Groovy при разработке приложения сервлета в моем IDE. Когда файл изменяется и переиздается в каталоге развертывания, он сначала удаляется перед повторным созданием. Поэтому событие модификации, которое запускается дважды на моей платформе Windows, можно игнорировать, а его аналог удаления нельзя использовать в моем контексте.
Это потому, что в настоящее время мы не можем отменить регистрацию сервлета, фильтра или прослушивателя из веб-контейнера. Таким образом, я не нашел оснований для включения такой функции в производство. Кроме того, в этом случае использования производительность даже не является проблемой, так как будет трудно иметь даже пять пакетов для наблюдения другим FileWatcher
экземпляром.
Джава
xxxxxxxxxx
1
Защищенный void loadScripts ( папка File ) {
2
if ( папка . существует ()) {
3
Файл [] файлы = папка . listFiles ();
4
if ( files ! = null ) {
5
для ( Файл файл : файлы ) {
6
if ( file . isFile ()) {
7
Объектный объект = scriptManager . loadScript ( файл );
8
регистр ( объект );
9
} еще {
10
loadScripts ( файл );
11
}
12
}
13
}
14
смотреть ( папка );
15
}
16
}
17
защищенные пустые часы ( папка с файлами )
19
новый FileWatcher ( папка ). addListener ( новый FileAdapter () {
20
21
public void onCreated ( событие FileEvent ) {
22
Файл файл = событие . getFile ();
23
if ( file . isFile ()) {
24
регистратор . информация ( «сценарий обработки» + файл . getName ());
25
процесс ( файл );
26
}
27
}
28
}). смотреть ();
29
}
30
защищенный процесс void ( файловый скрипт ) {
32
Объектный объект = scriptManager . loadScript ( скрипт );
33
// обновить приложение соответствующим образом
34
}
Хотя говорят, что использование Thread.sleep()
метода в модульном тесте - плохая идея, мы собираемся использовать его для написания тестового примера для FileWatcher
класса, поскольку нам нужна задержка между операциями.
Джава
xxxxxxxxxx
1
импортировать статическую орг . Junit . Утверждать . * ;
2
импорт Java . -й . Файл ;
3
импорт Java . -й . FileWriter ;
4
импорт Java . -й . IOException ;
5
импорт Java . Util . HashMap ;
6
импорт Java . Util . Карта ;
7
импорт орг . Junit . Тест ;
8
открытый класс FileWatcherTest {
10
12
public void test () выдает IOException , InterruptedException {
13
Папка файла = новый файл ( "src / test / resources" );
14
final Map < String , String > map = new HashMap <> ();
15
FileWatcher watcher = новый FileWatcher ( папка );
16
наблюдающий . addListener ( новый FileAdapter () {
17
public void onCreated ( событие FileEvent ) {
18
карта . put ( "file.created" , event . getFile (). getName ());
19
}
20
public void onModified ( событие FileEvent ) {
21
карта . put ( "file.modified" , event . getFile (). getName ());
22
}
23
public void onDeleted ( событие FileEvent ) {
24
карта . put ( "file.deleted" , event . getFile (). getName ());
25
}
26
}). смотреть ();
27
assertEquals ( 1 , watcher . getListeners (). size ());
28
ждать ( 2000 );
29
Файл файл = новый файл ( папка + "/test.txt" );
30
try ( FileWriter writer = новый FileWriter ( файл )) {
31
писатель . написать ( "Some String" );
32
}
33
ждать ( 2000 );
34
файл . удалить ();
35
ждать ( 2000 );
36
assertEquals ( file . getName (), map . get ( "file.created" ));
37
assertEquals ( file . getName (), map . get ( "file.modified" ));
38
assertEquals ( file . getName (), map . get ( "file.deleted" ));
39
}
40
public void wait ( int time ) выдает InterruptedException {
42
Автор . сон ( время );
43
}
44
}
46
В моей предыдущей статье « Groovify ваших Java-сервлетов (часть 2): создание сценариев JVM » я показал, как создать экземпляр объекта из сценария с помощью Groovy Script Engine с помощью простого ScriptManager
класса. Это может быть идеальной возможностью для меня исправить его реализацию, заменив устаревший Class.newInstance()
метод на Class.getConstructor().newInstance()
метод, чтобы сделать его правильным без исключений.
xxxxxxxxxx
1
импорт Java . -й . Файл ;
2
импорт Java . нетто . URL ;
3
импортная заводная . Util . GroovyScriptEngine ;
4
открытый класс ScriptManager {
6
защищенный финальный движок GroovyScriptEngine ;
8
public ScriptManager ( папка файлов ) {
10
engine = createScriptEngine ( папка );
11
}
12
защищенный GroovyScriptEngine createScriptEngine ( папка « Файл » ) {
14
URL [] urls = { папка . TOURI (). toURL ()};
15
вернуть новый GroovyScriptEngine ( URL , this . getClass (). getClassLoader ());
16
}
17
public Object loadScript ( String name ) {
19
вернуть двигатель . loadScriptByName ( имя ). getConstructor (). newInstance ()
20
}
21
}
23
Приведенный выше класс не может загружать сценарии, расположенные в подкаталогах данной папки, если вы не передадите относительный путь в аргументе имени сценария. Вот почему лучше написать это так:
xxxxxxxxxx
1
импорт Java . -й . Файл ;
2
импорт Java . нетто . URL ;
3
импортная заводная . Util . GroovyScriptEngine ;
4
открытый класс ScriptManager {
6
7
защищенная итоговая папка File ;
8
защищенный финальный движок GroovyScriptEngine ;
9
public ScriptManager ( папка файлов ) {
11
это . папка = папка ;
12
engine = createScriptEngine ();
13
}
14
защищенный GroovyScriptEngine createScriptEngine () {
16
URL [] urls = { папка . TOURI (). toURL ()};
17
вернуть новый GroovyScriptEngine ( URL , this . getClass (). getClassLoader ());
18
}
19
public Object loadScript ( File file ) {
21
Имя строки = файл . getAbsolutePath (). подстрока ( папка . getAbsolutePath (). length () + 1 );
22
вернуть двигатель . loadScriptByName ( имя ). getConstructor (). newInstance ()
23
}
24
}